-- 两横代表单行注释.
--[[
这样是多行注释.
--]]
----------------------------------------------------
-- 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