15 分钟学 Lua

-- 两横代表单行注释.

--[[
    这样是多行注释.
--]]

----------------------------------------------------
-- 1. 变量及流程控制.
----------------------------------------------------

num = 42  -- 数字是双精度的.
-- 别担心,64 位双精度里有 52 位
-- 用于存储精确 int 值;对于机器精度
-- 小于 52 位的整数来说不是问题

s = 'walternate'  -- 类似 Python 的字符串.
t = "双引号也行"
u = [[ 双中括号
可以赋值
多行字符串.]]
t = nil  -- 未定义变量 t; Lua 具有垃圾回收机制.

-- 用 do/end 这类关键字表示代码块:
while num < 50 do
  num = num + 1  -- 没有 ++ 或者 += 这样的运算符.
end

-- If 代码块:
if num > 40 then
  print('over 40')
elseif s ~= 'walternate' then  -- ~= 表示不等于.
  -- 等于用 == 表示; 字符串也可以用来比较.
  io.write('not over 40\n')  -- 默认控制台输出.
else
  -- 变量默认是全局的.
  thisIsGlobal = 5  -- 驼峰变量命名法.

  -- 这样是局部变量:
  local line = io.read()  -- 控制台读取输入.

  -- 字符串用 .. 连接起来:
  print('Winter is coming, ' .. line)
end

-- 未定义变量返回 nil.
-- 这样做不会报错:
foo = anUnknownVariable  -- 此时 foo = nil.

aBoolValue = false

-- 只有 nil 和 false 表示假; 0 和 '' 都是真!
if not aBoolValue then print('twas false') end

-- 'or' 两边一个真即为真; 'and' 两边一个假即为假.
ans = aBoolValue and 'yes' or 'no'  --> 永远返回 'no'

karlSum = 0
for i = 1, 100 do  -- 循环包含两端极值.
  karlSum = karlSum + i
end

-- 用 "100, 1, -1" 即可实现倒计数:
fredSum = 0
for j = 100, 1, -1 do fredSum = fredSum + j end

-- 总之, 循环范围用 起始值, 终值[, 步进值] 表示.

-- 另一种循环:
repeat
  print('the way of the future')
  num = num - 1
until num == 0


----------------------------------------------------
-- 2. 函数.
----------------------------------------------------

function fib(n)
  if n < 2 then return 1 end
  return fib(n - 2) + fib(n - 1)
end

-- 支持闭包及匿名函数:
function adder(x)
  -- 当 adder 被调用时
  -- 返回函数被创建, 而且记得参数 x:
  return function (y) return x + y end
end
a1 = adder(9)
a2 = adder(36)
print(a1(16))  --> 25
print(a2(64))  --> 100

-- 返回值, 函数调用参数和赋值语句
-- 都遵循一一对应的关系. 如果遇到长度不等的情况,
-- 不足的用 nil 弥补;
-- 多余的被丢弃.

x, y, z = 1, 2, 3, 4
-- 此时 x = 1, y = 2, z = 3, 然后4 被丢弃.

function bar(a, b, c)
  print(a, b, c)
  return 4, 8, 15, 16, 23, 42
end

x, y = bar('zaphod')  --> 输出 "zaphod  nil nil"
-- 此时 x = 4, y = 8, 然后 15..42 被丢弃.

-- 函数优先级最高, 可以是 local/global.
-- 下面两个等价:
function f(x) return x * x end
f = function (x) return x * x end

-- 下面两个也等价:
local function g(x) return math.sin(x) end
local g; g  = function (x) return math.sin(x) end
-- 这里 'local g' 声明意味着可以用 g 引用自己.

-- 顺带一提, 三角函数使用的是弧度制.

-- 用一个参数调用函数可以不带小括号:
print 'hello'  -- 正常工作.


----------------------------------------------------
-- 3. 表.
----------------------------------------------------

-- Table 是 Lua 里的唯一一种数据结构;
-- 类似于数组.
-- 像 php 的 arrays 或是 js 的 objects,
-- 它是由哈希索引的字典还能当列表使用.

-- 当作字典 / 映射使用举例:

-- 默认使用字符串作为索引键:
t = {key1 = 'value1', key2 = false}

-- 使用类似js的 . 操作符获取值:
print(t.key1)  -- 输出 'value1'.
t.newKey = {}  -- 增加一个键值对.
t.key2 = nil   -- 从表中去掉key2.

-- 中括号可以使任何 (非nil) 值作为键:
u = {['@!#'] = 'qbert', [{}] = 1729, [6.28] = 'tau'}
print(u[6.28])  -- 输出 "tau"

-- 对于字符串和数字的键, 可以直接索引.
-- 但是对于表的键, 索引的是它的哈希.
a = u['@!#']  -- 此时 a = 'qbert'.
b = u[{}]     -- 我们希望赋值 1729, 但是实际上赋的值是 nil:
-- b = nil 因为索引查找失败. 之所以失败
-- 此时的键是一个新表
-- 这个表的哈希与赋初值的时候并不相同.
-- 所以用字符串和数字当作键更便捷一点.

-- 表作为唯一参数的函数调用不用加小括号:
function h(x) print(x.key1) end
h{key1 = 'Sonmi~451'}  -- Prints 'Sonmi~451'.

for key, val in pairs(u) do  -- 表迭代.
  print(key, val)
end

-- _G 是一个内置全局表.
print(_G['_G'] == _G)  -- 输出 'true'.

-- 以表作为列表 / 数组:

-- 列表默认以数字为键:
v = {'value1', 'value2', 1.21, 'gigawatts'}
for i = 1, #v do  -- #v 代表v列表的长度.
  print(v[i])  -- 迭代从键 1 开始
end
-- 这里 '列表' 并不是一个专有类型. v 本质上是 table
-- 以连续数字为键的表, 可以当作列表使用.

----------------------------------------------------
-- 3.1 元表和元函数.
----------------------------------------------------

-- table 中可以设置元表
-- 可以用来覆写运算符. 后面还会看到
-- 类似js的prototype功能.

f1 = {a = 1, b = 2}  -- 代表分数 a/b.
f2 = {a = 2, b = 3}

-- 这样写肯定报错:
-- s = f1 + f2

metafraction = {}
function metafraction.__add(f1, f2)
  sum = {}
  sum.b = f1.b * f2.b
  sum.a = f1.a * f2.b + f2.a * f1.b
  return sum
end

setmetatable(f1, metafraction)
setmetatable(f2, metafraction)

s = f1 + f2  -- 这样写将会调用 f1 元表上的 __add(f1, f2) 函数

-- f1, f2 不含对自己元表的引用, 这一点
-- 不同于js的prototype, 要想取得元表必须使用
-- getmetatable(f1) 这样的函数. 元表本身也是表
-- 里面的键是Lua事先定义好的, 比如 __add.

-- 因为s没有设置元表所以下面这么写行不通:
-- t = s + s
-- 下面的类似面向对象的写法就能解决这个问题.

-- 使用元表的 __index 键可以制造类似 . 属性的读取操作:
defaultFavs = {animal = 'gru', food = 'donuts'}
myFavs = {food = 'pizza'}
setmetatable(myFavs, {__index = defaultFavs})
eatenBy = myFavs.animal  -- 多亏元表才可以这么写!

-- 表里没找到的键会在其元表的
-- __index 表里找, 没找到的话会在 __index 表元表的 __index 表里找, 如此递归地搜索下去.

-- __index 还能被赋予形如 function(tbl, key) 的函数
-- 以便在未找到键时直接自动调用.

-- 像 __index,add, .. 这些称作元函数.
-- 下面列举了元表里所有的预定义元函数.

-- __add(a, b)                     for a + b
-- __sub(a, b)                     for a - b
-- __mul(a, b)                     for a * b
-- __div(a, b)                     for a / b
-- __mod(a, b)                     for a % b
-- __pow(a, b)                     for a ^ b
-- __unm(a)                        for -a
-- __concat(a, b)                  for a .. b
-- __len(a)                        for #a
-- __eq(a, b)                      for a == b
-- __lt(a, b)                      for a < b
-- __le(a, b)                      for a <= b
-- __index(a, b)  <fn or a table>  for a.b
-- __newindex(a, b, c)             for a.b = c
-- __call(a, ...)                  for a(...)

----------------------------------------------------
-- 3.2 面向对象和继承.
----------------------------------------------------

-- Lua里没有类的概念; 只能通过
-- 表和元表来实现类似功能.

-- 代码下面有解释.

Dog = {}                                   -- 1.

function Dog:new()                         -- 2.
  newObj = {sound = 'woof'}                -- 3.
  self.__index = self                      -- 4.
  return setmetatable(newObj, self)        -- 5.
end

function Dog:makeSound()                   -- 6.
  print('I say ' .. self.sound)
end

mrDog = Dog:new()                          -- 7.
mrDog:makeSound()  -- 'I say woof'         -- 8.

-- 1. Dog 作为一个类; 实际上是个表.
-- 2. 函数 tablename:fn(...) 等价于
--    函数 tablename.fn(self, ...)
--    操作符 : 隐式插入了第一个参数 self.
--    至于self的取值请参考下面的 7 和 8.
-- 3. newObj 作为 Dog 类的实例.
-- 4. self = 继承的类. 通常
--    self = Dog, 但是继承会改变它.
--    newObj 具有 self 上的函数因为我们把
--    newObj 的元表和 self 的 __index 都设置为了 self.
-- 5. 要记得 setmetatable 函数会将其第一个参数作为返回值.
-- 6. 类似于上面的 2, 但是这次的
--    self 不是类而是实例.
-- 7. 等同于 Dog.new(Dog), 所以 new() 里面的 self = Dog.
-- 8. 等同于 mrDog.makeSound(mrDog); 这里的 self = mrDog.

----------------------------------------------------

-- 继承示例:

LoudDog = Dog:new()                           -- 1.

function LoudDog:makeSound()
  s = self.sound .. ' '                       -- 2.
  print(s .. s .. s)
end

seymour = LoudDog:new()                       -- 3.
seymour:makeSound()  -- 'woof woof woof'      -- 4.

-- 1. LoudDog 具有 Dog 的变量和函数.
-- 2. self 因为 new() 而具有一个 'sound' 键, 参见 3.
-- 3. 等同于 LoudDog.new(LoudDog), 然后查找元表得到
--    Dog.new(LoudDog) 因为 LoudDog 自己没有 'new' 键,
--    而其元表的 __index = Dog.
--    结果就是: seymour 的元表是 LoudDog, 然后
--    LoudDog.__index = LoudDog. 所以 seymour.key 就
--    = seymour.key, LoudDog.key, Dog.key, 这样就实现了
--    key 的继承.
-- 4. 'makeSound' 键在 LoudDog 里找到; 所以
--    等同于 LoudDog.makeSound(seymour).

-- 需要的话, 子类的 new() 和其父类相似:
function LoudDog:new()
  newObj = {}
  -- 实例化 newObj
  self.__index = self
  return setmetatable(newObj, self)
end

----------------------------------------------------
-- 4. 模块.
----------------------------------------------------


--[[ 我把这部分内容注释掉以便其能
--   作为 Lua 程序正常运行.
-- 假设我们有个文件 mod.lua, 其内容如下:
local M = {}

local function sayMyName()
  print('Hrunkner')
end

function M.sayHello()
  print('Why hello there')
  sayMyName()
end

return M

-- 另一个文件就可以使用 mod.lua 里的函数:
local mod = require('mod')  -- 运行 mod.lua 文件.

-- require 是加载模块的标准方法.
-- require 这里的作用相当于:     (没明白的话可以接着往下看)
local mod = (function ()
  <mod.lua 的脚本内容>
end)()
-- 就好像 mod.lua 是一个函数的函数体, 所以
-- mod.lua 里的本地变量在外面不能访问.

-- 这样可以运行因为这里的 mod = mod.lua 里的 M:
mod.sayHello()  -- Says hello to Hrunkner.

-- 这样不能运行; 因为 sayMyName 是 mod.lua 里的本地函数:
mod.sayMyName()  -- error

-- require 的返回值会自动缓存
-- 所以一个脚本只会执行一次, 即使它被 require 很多次.

-- 假设 mod2.lua 里包含函数 "print('Hi!')".
local a = require('mod2')  -- 输出 Hi!
local b = require('mod2')  -- 没有输出; a=b.

-- dofile 类似于 require 但并不缓存:
dofile('mod2.lua')  --> Hi!
dofile('mod2.lua')  --> Hi! (又执行了一次)

-- loadfile 载入 lua 文件但不运行它.
f = loadfile('mod2.lua')  -- 手动调用 f() 才会开始执行.

-- loadstring 是 loadfile 的字符串版本.
g = loadstring('print(343)')  -- 返回输出函数.
g()  -- 输出 343; 之前并没有输出.

--]]

----------------------------------------------------
-- 5. 参考.
----------------------------------------------------

--[[

我希望用 Löve 2D 游戏引擎做游戏, 所以开始学习 Lua. 这是动机.

我一开始从 BlackBulletIV 的 Lua for programmers 中学习.
然后阅读了官方的 Programming in Lua 电子书.
这是方法.

关于 Lua 的介绍可以参考 lua-users.org.

这里并没有介绍 Lua 的标准库:
 * string 库
 * table 库
 * math 库
 * io 库
 * os 库

其实本教程也是个 Lua 文件.
可以把它保存为 learn.lua 然后输入 "lua learn.lua" 来执行它!

这是我第一次给 tylerneylon.com 写稿. 
本教程也可以在 github gist 上找到. 类似
这样写法的其他语言的教程, 在这里:

http://learnxinyminutes.com/

祝你愉快学 Lua!

--]]

翻译自 Tyler Neylon 336.2013

4 Likes