由于函数以一种第一类值,因此一个显而易见的结果就是:函数不仅可以被存储在全局变量中,还可以被存储在表字段和局部变量中。
大部分 Lua
语言的库都是采用了将函数存储在表字段中的方式,例如: io.read
和 math.sin
。如下所示:
lib = {} lib.foo = function (x, y) return x + y end lib.goo = function (x, y) return x - y end print(lib.foo(2, 3), lib.goo(2, 3)) --> 5 -1
当然,也可以使用表构造器:
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
提示
在表字段中存储函数是 Lua
语言中实现面向对象编程的关键要素。
当把一个函数存储到局部变量时,就得到了一个局部变量,即一个被限定在指定作用域中使用的函数。局部函数对于包而言尤其有用:由于 Lua
语言将每个程序段作为一个函数处理,所以在一段程序中声明的函数就是局部函数,这些局部函数只在该程序段中可见。词法定界保证了程序段中的其他函数可以使用这些局部函数。
对于这种几部函数的使用, Lua
语言提供了一种语法糖:
local function f (params) body 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
而非局部的 fact
。我们可以通过先定义局部变量再定义函数的方式来解决这个问题:
local fact fact = function (n) if n == 0 then return 1 else return n * fact(n-1) -- 没有问题 end end
这样,函数内的 fact
指向的是局部变量。尽管在定义函数时,这个局部变量的值尚未确定,但到了执行函数时, fact
肯定已经有了正确的赋值。
当 Lua
原因展开局部函数的语法糖时,使用的并不是之前的基本函数定义。相反,形如:
local function foo (params) body end
的定义会被展开成:
local foo; foo = function (params) body end
因此,使用这种语法来定义递归函数不会有问题。
当然,这个技巧对于间接递归函数是无效的。在间接递归的情况下,必须使用与明确的前向声明等价的形式:
local f -- "前向"声明 local function g () some code f() some code end function f () some code g() some code end
注意
不能在最后一个函数定义前加上 local
。否则, Lua
语言会创建一个全新的局部变量 f
,从而使得先前声明的 f
(函数 g
中使用的那个)变为未定义状态。