在Lua中,函数是一种“第一类值”(和数字、字符串等一样,具有相同的权利),它们具有特定的词法域(一个函数可以嵌套在另一个函数中,内部函数可以访问外部函数中的变量)。函数式语言,函数式编程。

在Lua中有一个容易混淆的概念是,函数与其他所有值一样 都是匿名的,即它们都没有名称。当讨论一个函数名时(例如print),实际上是在讨论一个持有某函数的变量。这与其他变量持有各种值是一样的道理。可以以多种方式来操作这些变量。

a = {p = print}

a.p("Hello World")

print =math.sin

a.p(print(1))

sin = a.p

sin(10, 20)

Lua中最常见的就是 函数编写方式,如:

function foo(x) return 2 * x end

只是一种“语法糖”而已,上面这句只是一下代码 的一种简化书写形式:

foo = function (x) return 2 * x end

因此,一个函数定义实际上就是一条语句(更准确地说就是一条赋值语句),这条语句创建了一种类型为“函数”的值。并将这个值赋予一个变量。可以将表达式“function(x) <body> end”视为一种函数的构造式,就像table的构造式{}一样。将这种函数构造式的结果称为一个“匿名函数”。虽然一般情况下,会将函数赋予全局变量,即给予其一个名称。但在某些特殊情况中,仍会需要用到匿名函数。

table库中的函数table.sort,对table中的元素进行排序。sort函数没有提供排序准则(按升序或降序、按数字顺序等),而是提供了一个所谓”次序函数“。这个函数接受两个元素,并返回在有序情况下第一个元素是否应该排在第二个元素前面。

network = {

{name = "grauna", IP = "210.26.30.20"}

{name = "arraial", IP = "210.26.30.21"}

{name = "lua", IP = "210.26.30.22"}

{name = "derain", IP = "210.26.30.23"}

}

如果以name字段、按照反向的字符顺序来对这个table排序的话,只需要这么写:

table.sort(network, function (a, b) return ( > ) end)

像sort这样的函数,接受另一个函数作为其实参的,称其为一个“高阶函数”。

闭合函数closure

若将一个函数写在另一个函数之内,那么位于内部的函数便可以访问外部函数中的局部变量,这项特征称之为“词法域”。

function sortbygrade(names, grades)

table.sort(names, function(n1, n2) return grades[n1] > grades[n2] end)

end

内部的匿名函数可以访问外部函数sortbygrade的参数grades,即sortbygrade的局部变量。在这个匿名函数内部,grades既不是全局变量,也不是局部变量,将其称为一个“非局部的变量”。

从技术上讲,Lua中只有closure,而不存在“函数”,因为函数本身就是一种特殊的closure。

可以使用这样的技术来创建一个安全的运行环境,即所谓的“沙盒( sandbox)”

do

local oldOpen = io.open

local access_ok = function(filename ,mode)

<检查访问权限>

end

io.open = function(filename, mode)

if access_ok(filename, mode) then

return oldOpen(filename, mode)

else

return nil, "access denied"

end

end

end

经过重新定义后,一个程序就只能通过新的受限版本来调用原来的那个未受限制的open函数了。

非全局的函数

函数不仅可以存储在全局变量中,还可以存储在table的字段中和局部变量中。

将函数存储在table字段中,大部分Lua库都使用了这种机制。(io.read,math.sin)

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

}

或者

Lib = {}

function Lib.foo(x, y) return x + y end

function Lib.goo(x, y) return x - y end

局部函数

local f = function (参数)

<函数体>

end

或者(局部函数定义语法糖)

local function f(参数)

<函数体>

end

在定义递归的局部函数时,还有一个特别之处需要注意。

local fact = function (n)

if n == 0 then return 1

else return n * fact(n -1) --错误,这时局部的fact还未定义完,这里

的fact只能是表示全局函数fact

end

end

修改为:

local fact

fact = function (n)

if n == 0 then return 1

else return n * fact(n -1)

end

end

当Lua展开局部函数定义的语法糖时,并不是使用基本函数定义语法。而是对于局部函数定义:

local function foo (<参数>) <函数体> end

Lua将其展开为:

local foo

foo = function (<参数>) <函数体> end

因此,使用这种语法来定义递归函数不会产生错误:

local function fact(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中所引用的那个)置于未定义的状态。

正确的尾调用

Lua支持“尾调用消除”(类似goto的调用)。当一个函数调用是另一个函数的最后一个动作时,该调用才算是一条“尾调用”。

function f(x) return g(x) end --g(x)的调用就是一条尾调用

f调用完g之后就再无其他事情可做了。因此,程序就不需要返回那个“尾调用” 所在的函数了。在“尾调用”之后,程序也不需要保存任何关于该函数的栈信息了。有一些语言实现可以得益于这个特点,使得在进行“尾调用”时不耗费任何栈空间。将这种实现称之为支持“尾调用消除”。

“尾调用”不会耗费栈空间,所以一个程序可以拥有无数嵌套的“尾调用”。

在调用一下函数时,传入任何数字作为参数都不会引起栈溢出。

function foo (n)

if n > 0 then return foo(n - 1) end

end

在Lua中,只有“return <func>(<args>)”这样的调用形式才算是一条“尾调用”。Lua在调用前对<func>以及其参数求值,所以它们可以是任何复杂的表达式。如:

return x[i].foo(x[j] + a * b, i + j)

尾调用类似goto,因此在Lua中“尾调用”的一大应用就是编写“状态机”。这种程序通常以一个函数来表示一个状态,改变状态就是goto到另一个特定的函数。