闭包函数是什么
闭包函数是什么:
首先看如下代码,你是否了解其真正的意义:
function test() local i=0 return function() i=i+1 return i end end doTest=test() print(doTest()) --输出1 print(doTest()) --输出2
你可能有这样的疑问:
1. 调用test()返回的函数doTest()时,变量 i 的定义在哪?
2. test()中的i不是局部变量吗?为什么返回的函数还能调用。
如果你有这样的疑问,请接着往下看。
首先,了解几个概念:
词法定界:当一个函数内嵌套另一个函数的时候,内函数可以访问外部函数的局部变量,这种特征叫做词法定界。而这些变量就被称为该内嵌函数的upvalue,upvalue实际指的是变量而不是值,这些变量可以在内部函数之间共享。
闭包:通过调用含有一个内部函数加上该外部函数持有的外部局部变量(upvalue)的外部函数(外部函数就是工厂)产生的一个实例函数。
闭包组成:外部函数+外部函数创建的upvalue+内部函数(闭包函数)
如上面的函数test(),
test()就是外部函数,
外部函数的局部变量local i=0就是upvalue(也叫做非局部变量,之所以叫做非局部变量,是因为此变量的作用域既不是局部变量的作用域,也不是全局变量的作用域。),
返回的函数就是内部函数。
现在再回头看看原来的函数,我们知道了这种形式的函数叫做闭包函数。而test()中的局部变量i是内嵌函数的upvalue(非局部变量),且在内部函数中共享。
重复调用内部函数时,每一个调用都会记住上一次调用后的值,就是说第一次调用doTest()之后,i 的值已经是1了。
下面再看个例子
function test() local i=0 return function() i=i+1 return i end end doTest=test() doAgain=test() print(doTest()) --输出1 print(doTest()) --输出2 print(doAgain()) --输出1 print(doAgain()) --输出2
可以看到,此时执行doAgain()时 i 值并没有在原来的基础上增加。
原因是:
doTest,doAgain是建立在同一个函数,同一个局部变量的不同实例上面的两个不同的闭包。
调用一次test()就会产生一个新的闭包, 而闭包中的upvalue各自独立。所以不难解释为什么doAgain()的i 值为什么没有在原来的基础上增加了。
闭包函数有什么用:
在for in 的循环中需要使用到迭代器,而迭代器需要保留上一次调用的状态和下一次成功调用的状态,刚好可以使用闭包的机制来实现。
创建迭代器:
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 --[[这里的list_iter是一个工厂,每次调用都会产生一个新的闭包。该闭包内部包括了upvalue(t,i,n)。 因此每调用一次该函数产生的闭包,那么该闭包就会根据记录上一次的状态,以及返回list的下一个。]]
在while中使用:
--while中使用: t={10,20,90} iter=list_iter(t) --调用迭代器产生一个闭包 while true do --当闭包函数的i值已经等于n的值时,依然会执行闭包函数,此时返回的就是nil. --如果没有下面的判断,while就会一直循环,并进入死循环。 local element=iter() if element==nil then break end print(element) end
在泛型for使用
--泛型for使用: t={10,0,29} --这里的list_iter()工厂函数只会被调用一次产生一个闭包函数, --后面的每一次迭代都是用该闭包函数,而不是工厂函数。 for element in list_iter(t) do print(element) end 如果想要同时返回k,v值,需要修改工厂函数,如下所示: function list_iter(tb) local i = 0 return function () i = i + 1 --如果没有下面这个判断,就会一直执行。 --详情可以看我的另一篇博客,《Lua内容关于for循环的总结》 if tb[i] == nil then return nil end return i,tb[i] end end