- 类似 C 的转义符: '
\a
' (响铃), '\b
' (退格), '\f
' (表单), '\n
' (换行), '\r
' (回车), '\t
' (横向制表), '\v
' (纵向制表), '\
' (反斜杠), '\"
' (双引号), 以及 '\'
' (单引号)。 - 反斜杠加数字的形式
\ddd
注意,如果需要在这种描述方法后接一个是数字的字符, 那么反斜杠后必须写满三个数字。
- 下面五种方式描述了完全相同的字符串:
a = 'alo\n123"'
a = "alo\n123\""
a = 'lo923"'
a = [[alo
123"]]
a = [==[
alo
123"]==]
- Lua 中有八种基本类型: nil, boolean, number, string, function, userdata, thread, and table.
- Boolean 类型只有两种值:false 和 true。 nil 和 false 都能导致条件为假;而另外所有的值都被当作真。
- Number 表示实数(双精度浮点数)。
- userdata 类型用来将任意 C 数据保存在 Lua 变量中。 这个类型相当于一块原生的内存,除了赋值和相同性判断,Lua 没有为之预定义任何操作。 然而,通过使用 metatable (元表) ,程序员可以为 userdata 自定义一组操作。 userdata 不能在 Lua 中创建出来,也不能在 Lua 中修改。这样的操作只能通过 C API。 这一点保证了宿主程序完全掌管其中的数据。
- thread 类型用来区别独立的执行线程,它被用来实现 coroutine (协同例程)。 不要把 Lua 线程跟操作系统的线程搞混。 Lua 可以在所有的系统上提供对 coroutine 的支持,即使系统并不支持线程。
- table语言本身采用一种语法糖,支持以
a.name
的形式表示a["name"]。
- 特别的,因为函数本身也是值,所以 table 的域中也可以放函数。 这样 table 中就可以有一些 methods 了。
- table, function ,thread ,和 (full) userdata 这些类型的值是所谓的对象: 变量本身并不会真正的存放它们的值,而只是放了一个对对象的引用。 赋值,参数传递,函数返回,都是对这些对象的引用进行操作; 这些操作不会做暗地里做任何性质的拷贝。
- Boolean 类型只有两种值:false 和 true。 nil 和 false 都能导致条件为假;而另外所有的值都被当作真。
- Number 表示实数(双精度浮点数)。
- Lua 提供运行时字符串到数字的自动转换。
- 全局变量,局部变量,还有 table 的域。
- Lua 的一个执行单元被称作 chunk。 一个 chunk 就是一串语句段,它们会被循序的执行。 每个语句段可以以一个分号结束。lua 把一个 chunk 当作一个拥有不定参数的匿名函数 (参见 §2.5.9)处理。 正是这样,chunk 内可以定义局部变量,接收参数,并且返回值。
- 赋值:赋值段首先会做运算完所有的表达式,然后仅仅做赋值操作。 因此,下面这段代码
i = 3
i, a[i] = i+1, 20
- 会把
a[3]
设置为 20,而不会影响到a[4]
。 这是因为a[i]
中的i
在被赋值为 4 之前就被拿出来了(那时是 3 )。 简单说 ,这样一行
x, y = y, x
- 可以用来交换
x
和y
中的值。
- for 和 while:
for v = e1, e2, e3 do block end
- 用while表达:
do
local var, limit, step = tonumber(e1), tonumber(e2), tonumber(e3)
if not (var and limit and step) then error() end
while (step > 0 and var <= limit) or (step <= 0 and var >= limit) do
local v = var
block
var = var + step
end
end
- 注意:有个问题,for i=3,2,0 do print(i) end, 会进入死循环。
- 可变参数:函数调用和可变参数表达式都可以放在多重返回值中。 如果表达式作为一个独立语句段出现 (这只能是一个函数调用), 它们的返回列表将被对齐到零个元素,也就是忽略所有返回值。 如果表达式用于表达式列表的最后(或者是唯一)的元素, 就不会有任何的对齐操作(除非函数调用用括号括起来)。 在任何其它的情况下,Lua 将把表达式结果看成单一元素, 忽略除第一个之外的任何值。被括号括起来的表达式永远被当作一个值。所以,
(f(x,y,z))
即使f
返回多个值,这个表达式永远是一个单一值。 ((f(x,y,z))
的值是f
返回的第一个值。如果f
不返回值的话,那么它的值就是 nil 。)
f() -- 调整到 0 个结果
g(f(), x) -- f() 被调整到一个结果
g(x, f()) -- g 被传入 x 加上所有 f() 的返回值
a,b,c = f(), x -- f() 被调整到一个结果 ( c 在这里被赋为 nil )
a,b = ... -- a 被赋值为可变参数中的第一个,
-- b 被赋值为第二个 (如果可变参数中并没有对应的值,
-- 这里 a 和 b 都有可能被赋为 nil)
a,b,c = x, f() -- f() 被调整为两个结果
a,b,c = f() -- f() 被调整为三个结果
return f() -- 返回 f() 返回的所有结果
return ... -- 返回所有从可变参数中接收来的值
return x,y,f() -- 返回 x, y, 以及所有 f() 的返回值
{f()} -- 用 f() 的所有返回值创建一个列表
{...} -- 用可变参数中的所有值创建一个列表
{f(), nil} -- f() 被调整为一个结果
- 比较操作符:等于操作 (
==
) 首先比较操作数的类型。 如果类型不同,结果就是 false。 否则,继续比较值。 数字和字符串都用常规的方式比较。 对象 (table ,userdata ,thread ,以及函数)以引用的形式比较: 两个对象只有在它们指向同一个东西时才认为相等。 每次你创建一个新对象(一个 table 或是 userdata ,thread 函数), 它们都各不相同,即不同于上次创建的东西。 - and, or, 以及 not:and要找一个假的,or要找一个真的,找到就返回;到最后也找不到就返回最后的元素。
10 or 20 --> 10
10 or error() --> 10
nil or "a" --> "a"
nil and 10 --> nil
false and error() --> false
false and nil --> false
false or nil --> nil
10 and 20 --> 20
- 操作符的优先级:写在下表中,从低到高优先级排序:
or
and
< > <= >= ~= ==
..
+ -
* / %
not # - (unary)
^
- 通常,可以用括号来改变运算次序。 连接操作符 ('
..
') 和幂操作 ('^
') 是从右至左的。 其它所有的操作都是从左至右。下面是一些例子:
print(false or 5 and 6) --> 6
print(false or 5 and nil) --> nil
print(false or 2+3 >= 4*1 and not 7^2) --> false
print(false or 2+3 >= 4*1 and not nil) --> true
print(not nil) --> true
print(false or nil) --> nil
- 常用却不在意的元方法:
- "lt" 或是 "le":大小比较操作以以下方式进行。 如果参数都是数字,那么就直接做数字比较。 否则,如果参数都是字符串,就用字符串比较的方式进行。 再则,Lua 就试着调用 "lt" 或是 "le" 元方法 。
- concat:字符串的连接操作符写作两个点 ('
..
')。 如果两个操作数都是字符串或都是数字,连接操作将以 §2.2.1 中提到的规则把其转换为字符串。 否则,会取调用元方法 "concat"。
- 取长度操作符:写作一元操作
#
。 字符串的长度是它的字节数(就是以一个字符一个字节计算的字符串长度)。
tablet
的长度被定义成一个整数下标n
。 它满足t[n]
不是 nil 而t[n+1]
为 nil; 此外,如果t[1]
为 nil ,n
就可能是零。 对于常规的数组,里面从 1 到n
放着一些非空的值的时候, 它的长度就精确的为n
,即最后一个值的下标。 如果数组有一个“空洞” (就是说,nil 值被夹在非空值之间), 那么#t
可能是任何一个是 nil 值的位置的下标 (就是说,任何一个 nil 值都有可能被当成数组的结束)。 - 语法糖:
表:a.name
的形式表示a["name"];
- 函数:
v:name(args)
这个样子,被解释成v.name(v,args)
, 这里v
只会被求值一次。 - 函数:调用形式
f{fields}
是一种语法糖用于表示f({fields})
; 这里指参数列表是一个单一的新创建出来的列表; - 函数:形式
f'string'
(或是f"string"
亦或是f[[string]]
) 也是一种语法糖,用于表示f('string')
; 这里指参数列表是一个单独的字符串。 - 冒号语法可以用来定义方法, 就是说,函数可以有一个隐式的形参
self。 因此,如下写法:
function t.a.b.c:f (params) body end
是这样一种写法的语法糖:
t.a.b.c.f = function (self, params) body end
简化函数定义:
- 这样的写法:
function f () body end
被转换成
f = function () body end - 这样的写法:
function t.a.b.c.f () body end
被转换成
t.a.b.c.f = function () body end - 这样的写法:
local function f () body end
被转换成
local f; f = function () body end
注意,并不是转换成
local f = function () body end
(这个差别只在函数体内需要引用 f 时才有。)
尾调用:调用形式:return
functioncall堆栈项。 因此,对于程序执行的嵌套尾调用的层数是没有限制的。 然而,尾调用将删除调用它的函数的任何调试信息。 注意,尾调用只发生在特定的语法下, 这时, return有单一函数调用作为参数; 这种语法使得调用函数的结果可以精确返回。 因此,下面这些例子都不是尾调用:
return (f(x)) -- 返回值被调整为一个
return 2 * f(x)
return x, f(x) -- 最加若干返回值
f(x); return -- 无返回值
return x or f(x) -- 返回值被调整为一个
闭包:一个函数定义是一个可执行的表达式, 执行结果是一个类型为 function 的值。 当 Lua 预编译一个 chunk 的时候, chunk 作为一个函数,整个函数体也就被预编译了。 那么,无论何时 Lua 执行了函数定义, 这个函数本身就被实例化了(或者说是关闭了)。 这个函数的实例(或者说是closure(闭包)) 是表达式的最终值。 相同函数的不同实例有可能引用不同的外部局部变量, 也可能拥有不同的环境表。
形参,实参:当一个函数被调用, 如果函数没有被定义为接收不定长参数,即在形参列表的末尾注明三个点 ('...
'), 那么实参列表就会被调整到形参列表的长度, 变长参数函数不会调整实参列表; 取而代之的是,它将把所有额外的参数放在一起通过变长参数表达式传递给函数, 其写法依旧是三个点。 这个表达式的值是一串实参值的列表,看起来就跟一个可以返回多个结果的函数一样。 如果一个变长参数表达式放在另一个表达式中使用,或是放在另一串表达式的中间, 那么它的返回值就会被调整为单个值。 若这个表达式放在了一系列表达式的最后一个,就不会做调整了(除非用括号给括了起来)。
我们先做如下定义,然后再来看一个例子:
function f(a, b) end
function g(a, b, ...) end
function r() return 1,2,3 end
下面看看实参到形参数以及可变长参数的映射关系:
CALL PARAMETERS
f(3) a=3, b=nil
f(3, 4) a=3, b=4
f(3, 4, 5) a=3, b=4
f(r(), 10) a=1, b=10
f(r()) a=1, b=2
g(3) a=3, b=nil, ... --> (nothing)
g(3, 4) a=3, b=4, ... --> (nothing)
g(3, 4, 5, 8) a=3, b=4, ... --> 5 8
g(5, r()) a=5, b=1, ... --> 2 3
地方