lua语言有如下两个特点:
1.lua中的函数可以被存放在变量、表中,也可以作为函数的参数,还可以作为函数的返回值,如:
func = function()
print("Hello");
end
等价于
function func()
print("Hello");
end
这两种写法都是函数func()的定义;并且下面这个例子:
function func()
return function() print("Hello"); end --函数作为返回值
end
local f1 = func();
print(f1); --运行结果:function:00379738
f1(); --运行结果:Hello
是函数作为函数返回值的情况,其中f1是func()内部返回的函数,打印出来为函数在内存中的地址,f1()调用了内部函数,执行后打印出了"Hello"结果。
2.被嵌套的函数可以访问他外部函数中的变量(这个比较强悍,C语言只能访问全局的或以参数形式传入)。
看如下例子:
function func()
local index = 0;
local inner = function()
print(index);
index = index + 1;
end
inner(); --打印结果:0
inner(); --打印结果:1
print(index); --打印结果:2
end
func();
说明了func()中的局部变量index是可以在inner()中被使用和更改的,index被称为inner()的upvalue。
以上这两点是构成闭包的基础,也是lua语言的强大和灵活之处。
另外,将上面例子稍加改造,如下:
function func()
local index = 0;
print("Hello");
return function()
print(index);
index = index + 1;
end
end
local inner = func(); --调用func()函数,返回内部函数给inner,打印结果:"Hello"
print(inner); --打印结果:function:0037BE88
inner(); --调用内部函数,打印结果:0
inner(); --调用内部函数,打印结果:1
local other = func(); --获取另一个内部函数实例,打印结果:"Hello"
other(); --调用另一个内部函数实例,打印结果:0
other(); --同上,打印结果:1
由此可以看出函数的局部变量是可以保存在函数内部的,通过调用该函数内嵌的函数可以获取并修改局部变量的值,该函数的局部变量(upvalue)和内嵌函数的组合使用,形成了闭包,这看起来与C++中类的成员变量有些类似。函数具有闭包的功能后,不必再担心其局部变量没办法保存了,这一点对程序员的影响是很大的。
还有如下例子:
function func();
local index = 0;
return function()
index = index + 1;
return index;
end
end
local inner = func();
print(inner()); --打印结果:1
print(inner()); --打印结果:2
调用了func()的内部函数实例,并将函数调用结果打印了出来,是个很好的保存局部变量的方式。
lua中的函数是一阶类型值(first-class value),定义函数就象创建普通类型值相同(只不过函数类型值的数据主要是一条条指令而已),所以在函数体中仍然能定义函数。假设函数f2定义在函数f1中,那么就称f2为f1的内嵌(inner)函数,f1为f2的外包(enclosing)函数,外包和内嵌都具有传递性,即f2的内嵌必然是f1的内嵌,而f1的外包也一定是f2的外包。内嵌函数能访问外包函数已创建的所有局部变量,这种特性便是所谓的词法定界(lexical scoping),而这些局部变量则称为该内嵌函数的外部局部变量(external local variable)或upvalue。试看如下代码:
- function f1(n)
- -- 函数参数也是局部变量
- local function f2()
- print(n) -- 引用外包函数的局部变量
- end
- return f2
- end
- g1 = f1(1979)
- g1() -- 打印出1979
- g2 = f1(500)
- g2() -- 打印出500
可为什么g2和g1的函数体相同(都是f1的内嵌函数f2的函数体),但打印值不同?这就涉及到一个相当重要的概念——闭包(closure)。事实上,Lua编译一个函数时,会为他生成一个原型(prototype),其中包含了函数体对应的虚拟机指令、函数用到的常量值(数,文本字符串等等)和一些调试信息。在运行时,每当Lua执行一个形如function...end 这样的表达式时,他就会创建一个新的数据对象,其中包含了相应函数原型的引用、环境(environment,用来查找全局变量的表)的引用及一个由所有upvalue引用组成的数组,而这个数据对象就称为闭包。由此可见,函数是编译期概念,是静态的,而闭包是运行期概念,是动态的。g1和g2的值严格来说不是函数而是闭包,并且是两个不相同的闭包,而每个闭包能保有自己的upvalue值,所以g1和g2打印出的结果当然就不相同了。
使用upvalue非常方便,但他们的语义也非常微妙,需要引起注意。比如将f1函数改成:
- function f1(n)
- local function f2()
- print(n)
- end
- n = n + 10
- return f2
- end
- g1 = f1(1979)
- g1() -- 打印出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
闭包在创建之时其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
Lua将函数做为基本类型值并支持词法定界的特性使得语言具有强大的抽象能力。而透彻认识函数、闭包和upvalue将帮助程式员善用这种能力。
——————————————————————————————————————————————————————————
——————————————————————————————————————————————————————————
什么是闭包?
闭包并不是什么新奇的概念,它早在高级语言开始发展的年代就产生了。闭包(Closure)是词法闭包(Lexical Closure)的简称。对闭包的具体定义有很多种说法,这些说法大体可以分为两类:
一种说法认为闭包是符合一定条件的函数,比如参考资源中这样定义闭包:闭包是在其词法上下文中引用了自由变量的函数。
另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。比如参考资源中就有这样的的定义:在实现深约束时,需要创建一个能显式表示引用环境的东西,并将它与相关的子程序捆绑在一起,这样捆绑起来的整体被称为闭包。
这两种定义在某种意义上是对立的,一个认为闭包是函数,另一个认为闭包是函数和引用环境组成的整体。虽然有些咬文嚼字,但可以肯定第二种说法更确切。闭包只是在形式和表现上像函数,但实际上不是函数。函数是一些可执行的代码,这些代码在函数被定义后就确定了,不会在执行时发生变化,所以一个函数只有一个实例。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。所谓引用环境是指在程序执行中的某个点所有处于活跃状态的约束所组成的集合。其中的约束是指一个变量的名字和其所代表的对象之间的联系。那么为什么要把引用环境与函数组合起来呢?这主要是因为在支持嵌套作用域的语言中,有时不能简单直接地确定函数的引用环境。这样的语言一般具有这样的特性:
函数是一阶值(First-class value),即函数可以作为另一个函数的返回值或参数,还可以作为一个变量的值。
函数可以嵌套定义,即在一个函数内部可以定义另一个函数。