Lua中的函数是一阶类型值(first-class value),定义函数就象建立普通类型值同样(只不过函数类型值的数据主要是一条条指令而已),因此在函数体中仍然能够定义函数。假设函数f2定义在函数f1中,那么就称f2为f1的内嵌(inner)函数,f1为f2的外包(enclosing)函数,外包和内嵌都具备传递性,即f2的内嵌必然是f1的内嵌,而f1的外包也必定是f2的外包。内嵌函数能够访问外包函数已经建立的全部局部变量,这种特性即是所谓的词法定界(lexical scoping),而这些局部变量则称为该内嵌函数的外部局部变量(external local variable)或者upvalue(这个词多少会让人产生误解,由于upvalue实际指的是变量而不是值)。试看以下代码:
function f1(n)
-- 函数参数也是局部变量
local function f2()
print(n) -- 引用外包函数的局部变量
end
return f2
end
g1 = f1(1979)
g1() -- 打印出1979
g2 = f1(500)
g2() -- 打印出500
当执行完g1 = f1(1979)后,局部变量n的生命本该结束,但由于它已经成了内嵌函数f2(它又被赋给了变量g1)的upvalue,因此它仍然能以某种形式继续“存活”下来,从而令g1()打印出正确的值。
可为何g2与g1的函数体同样(都是f1的内嵌函数f2的函数体),但打印值不一样?这就涉及到一个至关重要的概念——闭包(closure)。事实上,Lua编译一个函数时,会为它生成一个原型(prototype),其中包含了函数体对应的虚拟机指令、函数用到的常量值(数,文本字符串等等)和一些调试信息。在运行时,每当Lua执行一个形如function...end 这样的表达式时,它就会建立一个新的数据对象,其中包含了相应函数原型的引用、环境(environment,用来查找全局变量的表)的引用以及一个由全部upvalue引用组成的数组,而这个数据对象就称为闭包。因而可知,函数是编译期概念,是静态的,而闭包是运行期概念,是动态的。g1和g2的值严格来讲不是函数而是闭包,而且是两个不相同的闭包,而每一个闭包能够保有本身的upvalue值,因此g1和g2打印出的结果固然就不同了。虽然闭包和函数是本质不一样的概念,但为了方便,且在不引发混淆的状况下,咱们对它们不作区分。
程序员
用用简单的话来讲upvalue:(也许不太严格,只为快速理解)
upvalue:嵌套函数的外部函数的局部变量 function func(a) -- 这个函数返回值是一个函数 return function () a = a + 1 -- 这里能够访问外部函数func的局部变量a,这个变量a就是upvalue return a end end
闭包:一个匿名函数加上其可访问的upvalue
使用upvalue很方便,但它们的语义也很微妙,须要引发注意。好比将f1函数改为:
function f1(n)
local function f2()
print(n)
end
n = n + 10
return f2
end
g1 = f1(1979)
g1() -- 打印出1989
内嵌函数定义在n = n + 10这条语句以前,可为何g1()打印出的倒是1989?upvalue实际是局部变量,而局部变量是保存在函数堆栈框架上(stack frame)的,因此只要upvalue尚未离开本身的做用域,它就一直生存在函数堆栈上。这种状况下,闭包将经过指向堆栈上的upvalue的引用来访问它们,一旦upvalue即将离开本身的做用域(这也意味着它立刻要从堆栈中消失),闭包就会为它分配空间并保存当前的值,之后即可经过指向新分配空间的引用来访问该upvalue。当执行到f1(1979)的n = n + 10时,闭包已经建立了,可是n并无离开做用域,因此闭包仍然引用堆栈上的n,当return f2完成时,n即将结束生命,此时闭包便将n(已是1989了)复制到本身管理的空间中以便未来访问。弄清楚了内部的秘密后,运行结果就不难解释了。
upvalue还能够为闭包之间提供一种数据共享的机制。试看下例:
function Create(n)
local function foo1()
print(n)
end
local function foo2()
n = n + 10
end
return foo1,foo2
end
f1,f2 = Create(1979)
f1() -- 打印1979
f2()
f1() -- 打印1989
f2()
f1() -- 打印1999
f1,f2这两个闭包的原型分别是Create中的内嵌函数foo1和foo2,而foo1和foo2引用的upvalue是同一个,即Create的局部变量n。前面已说过,执行完Create调用后,闭包会把堆栈上n的值复制出来,那么是否f1和f2就分别拥有一个n的拷贝呢?其实否则,当Lua发现两个闭包的upvalue指向的是当前堆栈上的相同变量时,会聪明地只生成一个拷贝,而后让这两个闭包共享该拷贝,这样任一个闭包对该upvalue进行修改都会被另外一个探知。上述例子很清楚地说明了这点:每次调用f2都将upvalue的值增长了10,随后f1将更新后的值打印出来。upvalue的这种语义颇有价值,它使得闭包之间能够不依赖全局变量进行通信,从而使代码的可靠性大大提升。
闭包在建立之时其upvalue就已经不在堆栈上的状况也有可能发生,这是由于内嵌函数能够引用更外层外包函数的局部变量:
function Test(n)
local function foo()
local function inner1()
print(n)
end
local function inner2()
n = n + 10
end
return inner1,inner2
end
return foo
end
t = Test(1979)
f1,f2 = t()
f1() -- 打印1979
f2()
f1() -- 打印1989
g1,g2 = t()
g1() -- 打印1989
g2()
g1() -- 打印1999
f1() -- 打印1999
执行完t = Test(1979)后,Test的局部变量n就“死”了,因此当f1,f2这两个闭包被建立时堆栈上根本找不到n的踪迹,这叫它们如何取得n的值呢?呵呵,不要忘了Test函数的n不只仅是inner1和inner2的upvalue,同时它也是foo的upvalue。t = Test(1979)以后,t这个闭包必定已经把n妥善保存好了,以后f一、f2若是在当前堆栈上找不到n就会自动到它们的外包闭包(姑且这么叫)的upvalue引用数组中去找,并把找到的引用值拷贝到本身的upvalue引用数组中。仔细观察上述代码,能够断定g1和g2与f1和f2共享同一个upvalue。这是为何呢?其实,g1和g2与f1和f2都是同一个闭包(t)建立的,因此它们引用的upvalue(n)实际也是同一个变量,而刚才描述的搜索机制则保证了最后它们的upvalue引用都会指向同一个地方。
Lua将函数作为基本类型值并支持词法定界的特性使得语言具备强大的抽象能力。而透彻认识函数、闭包和upvalue将帮助程序员善用这种能力。数组
更多:http://book.luaer.cn/_129.htm闭包
http://hi.baidu.com/gxvogbuwgucfsxe/item/26ae633a4e24f020b3c0c528框架