📄 lua 5.1 参考手册.txt
字号:
t[30] = 23
t[4] = 45 -- 4th exp
a = t
end
如果表单中最后一个域的形式是 exp ,而且其表达式是一个函数调用或者是一个可变参数,那么这个表达式所有的返回值将连续的进入列表(参见 §2.5.8)。为了避免这一点,你可以用括号把函数调用(或是可变参数)括起来(参见 §2.5)。
初始化域表可以在最后多一个分割符,这样设计可以方便由机器生成代码。
2.5.8 - 函数调用
Lua 中的函数调用的语法如下:
functioncall ::= prefixexp args
函数调用时,第一步,prefixexp 和 args 先被求值。如果 prefixexp 的值的类型是 function,那么这个函数就被用给出的参数调用。否则 prefixexp 的元方法 "call" 就被调用,第一个参数就是 prefixexp 的值,跟下来的是原来的调用参数(参见 §2.8)。
这样的形式
functioncall ::= prefixexp `:′ Name args
可以用来调用 "方法"。这是 Lua 支持的一种语法糖。像 v:name(args) 这个样子,被解释成 v.name(v,args),这里 v 只会被求值一次。
参数的语法如下:
args ::= `(′ [explist1] `)′
args ::= tableconstructor
args ::= String
所有参数的表达式求值都在函数调用之前。这样的调用形式 f{fields} 是一种语法糖用于表示 f({fields});这里指参数列表是一个单一的新创建出来的列表。而这样的形式 f'string' (或是 f"string" 亦或是 f[[string]])也是一种语法糖,用于表示 f('string');这里指参数列表是一个单独的字符串。
因为表达式语法在 Lua 中比较自由,所以你不能在函数调用的 '(' 前换行。这个限制可以避免语言中的一些歧义。比如你这样写
a = f
(g).x(a)
Lua 将把它当作一个单一语句段, a = f(g).x(a) 。因此,如果你真的想作为成两个语句段,你必须在它们之间写上一个分号。如果你真的想调用 f,你必须从 (g) 前移去换行。
这样一种调用形式:return functioncall 将触发一个尾调用。 Lua 实现了适当的尾部调用(或是适当的尾递归):在尾调用中,被调用的函数重用调用它的函数的堆栈项。因此,对于程序执行的嵌套尾调用的层数是没有限制的。然而,尾调用将删除调用它的函数的任何调试信息。注意,尾调用只发生在特定的语法下,这时, return 只有单一函数调用作为参数;这种语法使得调用函数的结果可以精确返回。因此,下面这些例子都不是尾调用:
return (f(x)) -- 返回值被调整为一个
return 2 * f(x)
return x, f(x) -- 最加若干返回值
f(x); return -- 无返回值
return x or f(x) -- 返回值被调整为一个
2.5.9 - 函数定义
函数定义的语法如下:
function ::= function funcbody
funcbody ::= `(′ [parlist1] `)′ block end
另外定义了一些语法糖简化函数定义的写法:
stat ::= function funcname funcbody
stat ::= local function Name funcbody
funcname ::= Name {`.′ Name} [`:′ Name]
这样的写法:
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 时才有。)
一个函数定义是一个可执行的表达式,执行结果是一个类型为 function 的值。当 Lua 预编译一个 chunk 的时候, chunk 作为一个函数,整个函数体也就被预编译了。那么,无论何时 Lua 执行了函数定义,这个函数本身就被实例化了(或者说是关闭了)。这个函数的实例(或者说是 closure(闭包))是表达式的最终值。相同函数的不同实例有可能引用不同的外部局部变量,也可能拥有不同的环境表。
形参(函数定义需要的参数)是一些由实参(实际传入参数)的值初始化的局部变量:
parlist1 ::= namelist [`,′ `...′] | `...′
当一个函数被调用,如果函数没有被定义为接收不定长参数,即在形参列表的末尾注明三个点 ('...'),那么实参列表就会被调整到形参列表的长度,变长参数函数不会调整实参列表;取而代之的是,它将把所有额外的参数放在一起通过变长参数表达式传递给函数,其写法依旧是三个点。这个表达式的值是一串实参值的列表,看起来就跟一个可以返回多个结果的函数一样。如果一个变长参数表达式放在另一个表达式中使用,或是放在另一串表达式的中间,那么它的返回值就会被调整为单个值。若这个表达式放在了一系列表达式的最后一个,就不会做调整了(除非用括号给括了起来)。
我们先做如下定义,然后再来看一个例子:
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
结果由 return 来返回(参见 §2.4.4)。如果执行到函数末尾依旧没有遇到任何 return 语句,函数就不会返回任何结果。
冒号语法可以用来定义方法,就是说,函数可以有一个隐式的形参 self。因此,如下写法:
function t.a.b.c:f (params) body end
是这样一种写法的语法糖:
t.a.b.c.f = function (self, params) body end
2.6 - 可视规则
Lua 是一个有词法作用范围的语言。变量的作用范围开始于声明它们之后的第一个语句段,结束于包含这个声明的最内层语句块的结束点。看下面这些例子:
x = 10 -- 全局变量
do -- 新的语句块
local x = x -- 新的一个 'x', 它的值现在是 10
print(x) --> 10
x = x+1
do -- 另一个语句块
local x = x+1 -- 又一个 'x'
print(x) --> 12
end
print(x) --> 11
end
print(x) --> 10 (取到的是全局的那一个)
注意这里,类似 local x = x 这样的声明,新的 x 正在被声明,但是还没有进入它的作用范围,所以第二个 x 指向的是外面一层的变量。
因为有这样一个词法作用范围的规则,所以可以在函数内部自由的定义局部变量并使用它们。当一个局部变量被更内层的函数中使用的时候,它被内层函数称作 upvalue(上值),或是 外部局部变量。
注意,每次执行到一个 local 语句都会定义出一个新的局部变量。看看这样一个例子:
a = {}
local x = 20
for i=1,10 do
local y = 0
a[i] = function () y=y+1; return x+y end
end
这个循环创建了十个 closure(这指十个匿名函数的实例)。这些 closure 中的每一个都使用了不同的 y 变量,而它们又共享了同一份 x。
2.7 - 错误处理
因为 Lua 是一个嵌入式的扩展语言,所有的 Lua 动作都是从宿主程序的 C 代码调用 Lua 库(参见 lua_pcall)中的一个函数开始的。在 Lua 编译或运行的任何时候发生了错误,控制权都会交还给 C ,而 C 可以来做一些恰当的措施(比如打印出一条错误信息)。
Lua 代码可以显式的调用 error 函数来产生一条错误。如果你需要在 Lua 中捕获发生的错误,你可以使用 pcall 函数。
2.8 - Metatable(元表)
Lua 中的每个值都可以用一个 metatable。这个 metatable 就是一个原始的 Lua table ,它用来定义原始值在特定操作下的行为。你可以通过在 metatable 中的特定域设一些值来改变拥有这个 metatable 的值的指定操作之行为。举例来说,当一个非数字的值作加法操作的时候, Lua 会检查它的 metatable 中 "__add" 域中的是否有一个函数。如果有这么一个函数的话,Lua 调用这个函数来执行一次加法。
我们叫 metatable 中的键名为 事件 (event) ,把其中的值叫作 元方法 (metamethod)。在上个例子中,事件是 "add" 而元方法就是那个执行加法操作的函数。
你可以通过 getmetatable 函数来查询到任何一个值的 metatable。
你可以通过 setmetatable 函数来替换掉 table 的 metatable 。你不能从 Lua 中改变其它任何类型的值的 metatable (使用 debug 库例外);要这样做的话必须使用 C API 。
每个 table 和 userdata 拥有独立的 metatable (当然多个 table 和 userdata 可以共享一个相同的表作它们的 metatable);其它所有类型的值,每种类型都分别共享唯一的一个 metatable。因此,所有的数字一起只有一个 metatable ,所有的字符串也是,等等。
一个 metatable 可以控制一个对象做数学运算操作、比较操作、连接操作、取长度操作、取下标操作时的行为, metatable 中还可以定义一个函数,让 userdata 作垃圾收集时调用它。对于这些操作,Lua 都将其关联上一个被称作事件的指定健。当 Lua 需要对一个值发起这些操作中的一个时,它会去检查值中 metatable 中是否有对应事件。如果有的话,键名对应的值(元方法)将控制 Lua 怎样做这个操作。
metatable 可以控制的操作已在下面列出来。每个操作都用相应的名字区分。每个操作的键名都是用操作名字加上两个下划线 '__' 前缀的字符串;举例来说,"add" 操作的键名就是字符串 "__add"。这些操作的语义用一个 Lua 函数来描述解释器如何执行更为恰当。
这里展示的用 Lua 写的代码仅作解说用;实际的行为已经硬编码在解释器中,其执行效率要远高于这些模拟代码。这些用于描述的的代码中用到的函数( rawget , tonumber ,等等。)都可以在 §5.1 中找到。特别注意,我们使用这样一个表达式来从给定对象中提取元方法
metatable(obj)[event]
这个应该被解读作
rawget(getmetatable(obj) or {}, event)
这就是说,访问一个元方法不再会触发任何的元方法,而且访问一个没有 metatable 的对象也不会失败(而只是简单返回 nil)。
"add": + 操作。
下面这个 getbinhandler 函数定义了 Lua 怎样选择一个处理器来作二元操作。首先,Lua 尝试第一个操作数。如果这个东西的类型没有定义这个操作的处理器,然后 Lua 会尝试第二个操作数。
function getbinhandler (op1, op2, event)
return metatable(op1)[event] or metatable(op2)[event]
end
通过这个函数, op1 + op2 的行为就是
function add_event (op1, op2)
local o1, o2 = tonumber(op1), tonumber(op2)
if o1 and o2 then -- 两个操作数都是数字?
return o1 + o2 -- 这里的 '+' 是原生的 'add'
else -- 至少一个操作数不是数字时
local h = getbinhandler(op1, op2, "__add")
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -