Lua中,函数是一种“第一类值”,它们具有特定的词法域。
“第一类值”表示在Lua中函数与其他传统类型的值具有相同的权利。函数可以存储到变量中(无论全局变量还是局部变量)或table中,可以作为实参传递给其他函数,还可以作为其他函数的返回值。
“词法域”是指,一个函数可以嵌套在另一个函数中,内部的函数可以访问外部函数中的变量。
Lua中一个容易混淆的概念是,函数与所有其他值一样都是匿名的,即它们都没有名称。当讨论一个函数名时,实际上是在讨论一个持有某函数的变量。与其他变量持有各种值是一个道理,可以用多种方式操作这些变量。
例如:
a = { p = print }
a.p("hello world") --hello world
print = math.sin --print现在引用了正弦函数
a.p( print(1) )--0.841470
sin = a.p --sin现在引用了print函数
sin( 10 ,20 ) --10 20
函数就是由一些表达式创建的。
例如:
常见的函数编写方式:
function foo ( x ) return 2*x end
可以将上面函数编写的代码换成一种简化书写形式:
foo = function( x ) return 2*x end
函数在Lua中是一种"第一类值",所以不仅可以将其存储在全局变量中,还可以存储在局部变量中甚至table的字段值中。
closure(闭合函数)
词法域:
names = { "Peter", "Paul", "Mary" }
grades = { Mary = 10, Paul = 7, Peter = 8 }
table.sort( names, function( n1, n2)
return grades[n1] > grades[n2]
end
)
现在创建一个函数来做:
function sortbygrade( names, grades )
table.sort( names, function( n1, n2 )
return grades[n1] > grades[n2] -- grades非局部变量
end)
end
传递给sort的匿名函数可以访问参数grades,而grades是外部函数sortbygrade的局部变量。在这个匿名函数内部,grades既不是全局变量也不是局部变量,将其称为一个"非局部的变量"。只所以存在这种访问,因为函数在Lua中是"第一类值"。
function newCounter()
local i = 0
return function() -- 匿名函数
i = i + 1
return i
end
end
c1 = newCounter()
print( c1() ) -- 1
print( c1() ) -- 2
匿名函数访问了一个"非局部的变量i",该变量用于保持一个计数器。
一个closure就是一个函数加上该函数所需访问的所有"非局部的变量"。如果再次调用newCounter,那么它会创建一个新的局部变量i,从而也将得到一个closure
c2 = newCounter()
print( c2() ) -- 1
print( c1() ) -- 3
print( c2() ) -- 2
c1和c2 是同一个函数所创建的两个不同的closeure,它们各自拥有局部变量i的独立实例。
Lua中函数是存储在普通变量中的,因此可以轻易地重新定义某些函数,甚至是重新定义那些预定义的函数。通常重新定义一个函数时,需要在新的实现中调用原来的那个函数。例如:重新定义函数sin,使其参数能使用角度来代替原来的弧度。那么这个新函数就必须得转换它的实参,并调用原来的sin函数来完成真正的计算。
oldSin = math.sin
math.sin = function(x)
return oldSin( x*math.pi/180 )
end
另有一种更彻底的做法:
do
local oldSin = math.sin
local k = math.pi/180
math.sin = function(x)
return oldSin(x*k)
end
end
非全局的函数
在Lua中创建函数,只需要将常规的函数语法与table语法结合起来使用即可
例如:
Lib = {}
Lib.foo = function(x, y) return x + y end
Lib.goo = function(x, y) return x- y end
还可以使用构造式
Lib = {
foo = function(x,y) return x + y end
goo = function(x,y) return x - y end
}
Lua中还提供了另一种语法来定义
Lib = {}
function Lib.foo ( x, y ) return x + y end
function Lib.goo( x,y ) return x - y end
将一个函数存储到一个局部变量中,即得到一个"局部函数",也就是说该函数只能在某个特定的作用域中使用。词法域确保了程序包中的其他函数可以使用这些局部函数:
local f = function(<参数>)
<函数体>
end
local g = function(<参数>)
<一些代码>
f() -- f在这里是可见的
<一些代码>
end
对于这种局部函数的定义,Lua还支持一种特殊"语法糖":
local function f(<参数>)
<函数体>
end
在定义递归的局部函数时,还有一个特别之处需要注意。例如下面代码:
local fact = function (n)
if n == 0 then return 1
else return n * fact(n-1) -- 错误
end
end
当Lua编译到函数体中调用fact(n-1)的地方时,由于局部的fact尚未定义完毕,因此这句表达式其实调用了一个全局的fact,而非此函数自身。为了解决此问题,可以先定义一个局部变量然后再定义函数本身:
local fact
fact = function ( n )
if n == 0 then return 1
else return n*fact(n-1)
end
end
对于间接递归的情况中,必须使用一个明确的前向声明
local f, g --前向声明
function g()
<一些代码>
f()
<一些代码>
end
function f()
<一些代码>
g()
<一些代码>
end
注意不要把第二个函数定义写为"local function f"。如果那样的话,Lua会创建一个全新的局部变量f,而将原来声明的f(函数g中所引用的那个)置于未定义的状态。
正确的尾调用
所谓尾调用就是一种类似于goto的函数调用。当一个函数调用是另一个函数的最后一个动作时,该调用才算是一条“尾调用”。
例如:
function f (x) return g(x) end
当f调用完g之后就再无其他事情可做了。当g返回时,执行控制权可以直接返回到调用f的那个点上。使得在进行"尾调用"时不耗费任何栈空间。这种实现称为“尾调用消除”。
判断是否为"尾调用"的准则是:一个函数在调用完另一个函数之后,是否就无其他事情需要做了。
function f (x) g (x) end
这个示例问题在于,当调用完g后,f并不能立即返回,它需要丢弃g返回的临时结果。
不符合尾调用的准则的例子
return g(x) + 1 -- 必须做一次加法
return x or g(x) --必须调整为一个返回值
return ( g( x ) ) --必须调整为一个返回值
在Lua中,只有"return <func>(args)"这样的调用形式才算是一条"尾调用"。