子函数可以使用父函数中的局部变量,这种行为叫做闭包
闭包组成:外部函数+外部函数创建的upvalue+内部函数
function test()
--函数作为返回值,这里的i也叫外部局部变量,就是lua中的upvalue
local i = 0
return function ( ... ) --注意这里是返回函数的地址,不是执行
i = i +1
return i
end
end
c1 = test() --接收函数返回的地址
print(test()) -->function: 0x0116893168
print(c1()) -->1
print(c1()) -->2 --local i = 0的意思是重新创建一个新的变量,这里没有创建新的
c2 = test() --再次调用test,将创建一个新的局部变量i
print(c2()) -->1
print(c2()) -->2
--c1,c2是建立在同一个函数,同一个局部变量的不同实例上面的两个不同的闭包
--闭包中的upvalue各自独立,调用一次test()就会产生一个新的闭包
问题:为什么c1能打印出值
当执行完c1 = test()
后,局部变量i
的生命本该结束,但因为他已成了内嵌函数function
(他又被赋给了变量c1
)的upvalue
,所以他仍然能以某种形式继续“存活”下来,从而令c1()
打印出正确的值
问题:为什么c1和c2的函数体相同(都是test的内嵌函数function的函数体),但打印值会不同?
Lua
编译一个函数时,会为他生成一个原型(prototype
),其中包含了函数体对应的虚拟机指令、函数用到的常量值(数,文本字符串等等)和一些调试信息。在运行时,每当Lua
执行一个形如function...end
这样的表达式时,他就会创建一个新的数据对象,其中包含了相应函数原型的引用、环境(environment
,用来查找全局变量的表)的引用及一个由所有upvalue
引用组成的数组,而这个数据对象就称为闭包。由此可见,函数是编译期概念,是静态的,而闭包是运行期概念,是动态的。c1
和c2
的值严格来说不是函数而是闭包,并且是两个不相同的闭包,而每个闭包能保有自己的upvalue值,所以c1
和c2
打印出的结果当然就不相同了。
function f1( n )
local function f2 ( ... )
n = n + 1
print(n)
end
return f2
end
c1 = f1(100)
c1() -->101
c1() -->102
c2 = f1(200)
c2() -->201
c2() -->202
如果将以上代码更改为:
function f1( n )
local function f2 ( ... )
n = n + 1
print(n)
end
n = n + 10 --先执行n=n+10 再调用f2函数
return f2
end
c1 = f1(100) --c1指向函数f2
c1() -->111 --执行f2函数
c1() -->112 --执行f2函数
c2 = f1(200) --c1指向函数f1地址,还没有执行f2函数
c2() -->211 --执行f2函数
c2() -->212 --执行f2函数
问题:内嵌函数定义在n = n + 10这条语句之前,可为什么c1()打印出的却是111?
upvalue
实际是局部变量,而局部变量是保存在函数堆栈框架上(stack frame
)的,所以只要upvalue
还没有离开自己的作用域,他就一直生存在函数堆栈上。这种情况下,闭包将通过指向堆栈上的upvalue
的引用来访问他们,一旦upvalue
即将离开自己的作用域(这也意味着他马上要从堆栈中消失),闭包就会为他分配空间并保存当前的值,以后便可通过指向新分配空间的引用来访问该upvalue
。当执行到f1(100)的n = n + 10
时,闭包已创建了,不过n
并没有离开作用域,所以闭包仍然引用堆栈上的n
,当return f2
完成时,n
即将结束生命,此时闭包便将n
(已是111了)复制到自己管理的空间中以便将来访问。
2:闭包在迭代器中的使用
迭代器需要保留上一次调用的状态和下一次成功调用的状态,刚好可以使用闭包的机制来实现
a) 迭代器的创建
--这里的list_iter是一个工厂,每次调用都会产生一个新的闭包该闭包内部包括了upvalue(t,i,n)
--因此每调用一次该函数产生的闭包那么该闭包就会根据记录上一次的状态,以及返回list的下一个
function list_iter( t )
local i = 0
local n = table.getn(t)
return function ( ... )
i = i +1
if i <= n then
return t[i]
end
end
end
b)迭代器的使用
--while中使用:
local table1 = {"7","8","9","10","11"}
c1 = list_iter(table1)--调用迭代器产生一个闭包
while true do
local element = c1()
if element == nil then
break
end
print(element)
end
输出结果:
[LUA-print] 7
[LUA-print] 8
[LUA-print] 9
[LUA-print] 10
[LUA-print] 11
--泛型for使用:
local table1 = {"7","8","9","10","11"}
for element in list_iter(table1) do --这里的list_iter()工厂函数只会被调用一次产生一个闭包函数,后面的每一次迭代都是用该闭包函数,而不是工厂函数
print(element)
end
输出结果:
[LUA-print] 7
[LUA-print] 8
[LUA-print] 9
[LUA-print] 10
[LUA-print] 11
网友评论