2.5.8 – 函数定义
函数定义的语法是
function ::= function funcbody
funcbody ::= `(´ [parlist1] `)´ block end
下面的甜心语法简化了函数的定义:
stat ::= function funcname funcbody
stat ::= local function Name funcbody
funcname ::= Name {`.´ Name} [`:´ Name]
语句
function f () ... end
可翻译为
f = function () ... end
语句
function t.a.b.c.f () ... end
可翻译为
t.a.b.c.f = function () ... end
语句
local function f () ... end
可翻译为
local f; f = function () ... end
函数是可以执行的表达式,其值类型是function.当Lua预编译一个块时,所有它的函数体也被编译了。之后,无论何时Lua执行该函数定义,函数都被实例化(或者封闭)。这个函数实例(或者闭包)就是表达式的最终值。同一个函数的不同实例可能会引用到不同的外部局部变量并可能有不同的环境表。
形参作为局部变量由实参值初始化:
parlist1 ::= namelist [`,´ `...´]
parlist1 ::= `...´
当函数被定义时,实参列表被调整为形参列表的长度,除非该函数是variadic或变参函数,此种函数通过在其形参列表后面加三个点号(`...´)来标识。变参函数不会调整其形参列表,而是将所有额外的参数收集在一个称作arg的隐含参数中。arg的值是表,用一个表域n来保存额外参数的个数,并将这些参数保存在1,2,…,n的位置。
考虑下面例子中的定义:
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, arg={n=0}
g(3, 4) a=3, b=4, arg={n=0}
g(3, 4, 5, 8) a=3, b=4, arg={5, 8; n=2}
g(5, r()) a=5, b=1, arg={2, 3; n=2}
结果通过return语句返回(参见2.4.4)。如果程序执行到函数末端而没有遇到return语句,则函数将不返回结果。
冒号语法用来定义方法,即,拥有一个叫作self的隐含参数的函数。因此,语句
function t.a.b.c:f (...) ... end
是下面语句的甜心语法
t.a.b.c.f = function (self, ...) ... end
2.6 – 可见性规则Lua是一种lexically scoped语言。参数的作用域从其被声明后的第一条语句开始,到其声明所在的最内层block的末尾结束。例如:
x = 10 -- 全局变量
do -- 新的block
local x = x -- 新的`x', 值为10
print(x) --> 10
x = x+1
do -- 另一个block
local x = x+1 -- 另一个`x'
print(x) --> 12
end
print(x) --> 11
end
print(x) --> 10 (全局x)
注意,在local x = x这样的声明中,被声明的新x还不在其作用域内,而第二个x引用的是一个外部变量。
由于lexical scoping规则,局部变量可被定义在其作用域内的函数自由访问,例如:
local counter = 0
function inc (x)
counter = counter + x
return counter
end
由一个内部函数使用的局部变量,在这个函数内部被称作上值,或者外部局部变量。
注意每次执行一条局部语句都会定义新的局部变量。考虑下面的例子:
a = {}
local x = 20
for i=1,10 do
local y = 0
a[i] = function () y=y+1; return x+y end
end
循环创建了十个闭包(即十个匿名函数的实例)。每个闭包都使用一个不同的y变量,而它们都共用一个x。
2.7 - 错误处理因为Lua是一个扩展语言,所有的Lua动作都从其宿主程序里调用一个Lua库中的函数的C代码开始。在Lua编译或执行时若产生错误,控制会被返回给C,由它来作适当的处理(例如打印一个错误消息)。
Lua代码可以通过调用error函数显式的产生一个错误(参见5.1)。如果你需要捕获Lua中的错误,你可以使用pcall函数(参见5.1)。
2.8 – 元表Lua中每个表和userdata对象都可以有一个元表(metatable)。元表是一个普通的Lua表,其中定义了原始表和userdata在特定操作下的行为。你可以通过设定一个对象的元表中的特定域来改变该对象的一些行为面貌。例如,当对象是加法操作的操作数时,Lua会检查它的元表中“__add”域对应的函数。如果找到了,Lua便调用那个函数来执行加法。
我们将元表中的键称作事件(events),值称作元方法(metamethods)。在上一个例子中,事件是“add”,元方法是执行加法操作的函数。
你可以通过set/getmetatable函数来查询和更改一个对象的元表(参见5.1)。
元表可以控制一个对象的在算术运算、次序比较运算、连接和索引时的行为。元表也可以定义userdata对象被作为垃圾回收时调用的函数。对上述的每一种操作Lua都为之关联了一个特定的键,称作事件。当Lua为一个表或者userdata执行那些操作时,它会检查对象是否有一个包含相应事件的元表。如果是,与该键关联的值(元方法)将控制Lua如果执行操作。
元表控制下面列出的操作。每个操作都由其对应的名字来标识。每个操作的键是以双下划线为前缀加上其名字组成的字符串;例如,“add”操作的键是“__add”。这些操作的语意用一个描述解释器如何执行它们的Lua函数作了更好的说明。
这里显示的Lua代码只是说明性的;其真实行为已被硬编码在解释器中,并且比这里的模拟代码高效许多。以下说明中用到的所有函数(rawget, tonumber, 等等)都在5.1中作了说明。特别地,要获得一个给定对象的元方法,我们使用下面的语句
metatable(obj)[event]
这可以被读作
rawget(metatable(obj) or {}, event)
即,对元方法的访问不会调用其它的元方法,对没有元表的对象的访问也不会失败(只是简单地返回nil)。
"add": the + operation. + 操作下面的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 -- both operands are numeric?
return o1 + o2 -- `+' here is the primitive `add'
else -- at least one of the operands is not numeric
local h = getbinhandler(op1, op2, "__add")
if h then
-- call the handler with both operands
return h(op1, op2)
else -- no handler available: default behavior
error("...")
end
end
end
"sub": - 操作。行为与“add”操作相似。"mul": * 操作。行为与“add”操作相似。"div": / 操作。行为与“add”操作相似。"pow": ^ (求幂) 操作. function pow_event (op1, op2)
local o1, o2 = tonumber(op1), tonumber(op2)
if o1 and o2 then -- both operands are numeric?
return __pow(o1, o2) -- call global `__pow'
else -- at least one of the operands is not numeric
local h = getbinhandler(op1, op2, "__pow")
if h then
-- call the handler with both operands
return h(op1, op2)
else -- no handler available: default behavior
error("...")
end
end
end
"unm": 一元 – 操作。 function unm_event (op)
local o = tonumber(op)
if o then -- operand is numeric?
return -o -- `-' here is the primitive `unm'
else -- the operand is not numeric.
-- Try to get a handler from the operand
local h = metatable(op).__unm
if h then
-- call the handler with the operand and nil
return h(op, nil)
else -- no handler available: default behavior
error("...")
end
end
end
"concat": .. (连接) 操作. function concat_event (op1, op2)
if (type(op1) == "string" or type(op1) == "number") and
(type(op2) == "string" or type(op2) == "number") then
return op1 .. op2 -- primitive string concatenation
else
local h = getbinhandler(op1, op2, "__concat")
if h then
return h(op1, op2)
else
error("...")
end
end
end
"eq": == 操作。getcomphandler函数定义了Lua如何为比较操作符选择元方法。只有当两个被比较的对象是相同类型且其对选定操作的元方法相同时,该元方法才被选中。
function getcomphandler (op1, op2, event)
if type(op1) ~= type(op2) then return nil end
local mm1 = metatable(op1)[event]
local mm2 = metatable(op2)[event]
if mm1 == mm2 then return mm1 else return nil end
end
“eq”事件的定义如下:
function eq_event (op1, op2)
if type(op1) ~= type(op2) then -- different types?
return false -- different objects
end
if op1 == op2 then -- primitive equal?
return true -- objects are equal
end
-- try metamethod
local h = getcomphandler(op1, op2, "__eq")
if h then
return h(op1, op2)
else
return false
end
end
a ~= b 等价于 not (a == b).
"lt": < 操作。function lt_event (op1, op2)
if type(op1) == "number" and type(op2) == "number" then
return op1 < op2 -- numeric comparison
elseif type(op1) == "string" and type(op2) == "string" then
return op1 < op2 -- lexicographic comparison
else
local h = getcomphandler(op1, op2, "__lt")
if h then
return h(op1, op2)
else
error("...");
end
end
end
a > b 等价于 b < a.
"le": the <= operation. function le_event (op1, op2)
if type(op1) == "number" and type(op2) == "number" then
return op1 <= op2 -- numeric comparison
elseif type(op1) == "string" and type(op2) == "string" then
return op1 <= op2 -- lexicographic comparison
else
local h = getcomphandler(op1, op2, "__le")
if h then
return h(op1, op2)
else
h = getcomphandler(op1, op2, "__lt")
if h then
return not h(op2, op1)
else
error("...");
end
end
end
end
a >= b 等价于 b <= a。
注意,在没有“le”元方法时,Lua会尝试“lt”,假定a<=b等价于not (b<a)。
"index": 索引访问table[key] function gettable_event (table, key)
local h
if type(table) == "table" then
local v = rawget(table, key)
if v ~= nil then return v end
h = metatable(table).__index
if h == nil then return nil end
else
h = metatable(table).__index
if h == nil then
error("...");
end
end
if type(h) == "function" then
return h(table, key) -- call the handler
else return h[key] -- or repeat operation on it
end
"newindex": 索引赋值table[key] = value function settable_event (table, key, value)
local h
if type(table) == "table" then
local v = rawget(table, key)
if v ~= nil then rawset(table, key, value); return end
h = metatable(table).__newindex
if h == nil then rawset(table, key, value); return end
else
h = metatable(table).__newindex
if h == nil then
error("...");
end
end
if type(h) == "function" then
return h(table, key,value) -- call the handler
else h[key] = value -- or repeat operation on it
end
"call": 当Lua调用一个值时被调用 function function_event (func, ...)
if type(func) == "function" then
return func(unpack(arg)) -- primitive call
else
local h = metatable(func).__call
if h then
return h(func, unpack(arg))
else
error("...")
end
end
end