感谢前人的分享 :Lua的upvalue和闭包
首先我们先来举一个c++中函数的例子,我们声明了一个函数,例如
void printTest()
{
cout<<"just print";
}
这里函数名printTest
实质上存放的是该函数的函数地址。然后我们再开看Lua
中的函数,看看有什么区别。
然后我们再来看lua。lua中的函数有三点需要注意的地方:
1.在Lua语言中,函数是第一类值。这意味着函数和其他类型(例如 int float)是相同的,你既可以把他当作函数的参数传入 也可以当作返回值传出。
2.在lua中,所有函数都是匿名的。像其他所有值一样,函数并没有名字。当讨论函数名时,比如 print,实际上值得是保存该函数的变量。
3.从技术上面讲,lua中其实只有闭包没有函数。函数本身只是闭包的一种原型。
这里前两点比较好理解我们跳过着重讲一下第三点。那么什么事闭包?先看个简单地示例
function newCounter()
local count=0
return function()
count = count + 1
return count
end
end
c1 = newCounter()
print(c1()) --> 1
print(c1()) --> 2
c2 = newCounter()
print(c2()) --> 1
这里我们发现了一个很有意思的现象。就是在newCounter
中,count
的值被保存了下来。其根本原因是因为在这里,count
其实是一个 上值
(upvalue)
也叫作非局部变量
(non-local variable)
。
在这里我们有函数newCounter
和一个匿名函数记作f
。其中f
是newCounter
的内嵌函数,newCounter
是f
的外包函数。这两种性质同样具有传递性。即f
的内嵌函数同时也是newCounter
的内嵌函数,newCounter
的外包函数同时也是f
的外包函数。而被内嵌函数访问的外包函数中的变量,便是该内嵌函数的上值。
这里我们搞清楚了什么上值,但上值又是怎么保存下来的呢?这就关系到我们上面所说的第三点:闭包。
在
lua
编译一个函数的时候,其中办函了函数体对应的虚拟机指令、函数用到的常量值(数、字符串等等)和一些调试信息。在运行时,每当lua
执行一个形如function...end
这样的函数是,它就会创建一个新的数据对象,其中包含了响应函数原型的引用、环境(用来查找全局变量、方法的表)的引用以及一个有所有upvalue
数据组成的数组,而这个数据对象就成为闭包。
我们就会发现,其实在lua
中,如开头所举的c++
中函数只是编译期的概念。而在真正运行的时候实质上都是闭包。而上面例子中的c1、c2
严格来说不是函数而是闭包。而且c1、c2
分别是两个不同的闭包,他们有着各自的upvalue
值所以每次打印出来的值都被分别保存了下来。
而关于upvalue
,他的实质其实是局部变量,而局部变量是保存在函数堆栈框架上的。所以只要upvalue
还没有离开自己的作用域,他就一直生存在函数堆栈上。这时闭包通过指向堆栈上upvalue
的引用来方位他们。而一旦upvalue
离开了自己的作用域,在被堆栈消除之前,闭包就会为它分配空间并保存当前的值,以后便可以通过指向新分配的空间的引用来访问upvalue
。
upvalue和闭包数据共享
在我们对闭包和upvalue
的概念有所了解后我们来看两种用法
1.单重内嵌函数的闭包(函数创建的闭包)
function newCounter()
local count = 0
function f1()
count = count + 1
return count
end
function f2()
print(count)
end
return f1,f2
end
g1 , g2 = newCounter()
g1()
g2() -->1
g1()
g2() -->2
g1、g2
两个闭包的原型分别是f1、f2
两个函数,而这两个闭包同时指向了一个相同的upvalue : count
。在局部变量的作用域结束的时候,系统发现g1 g2
两个闭包分别指向了相同的 upvalue
,系统便只生成了 一个拷贝供两个闭包共同使用。此时任意一个闭包对该数据进行操作都会影响到另一个闭包。而这种操作的优点在于两个闭包之间可以不依赖于全局变量进行通信,并且该 upvalue
也相对较安全。
2.多重内嵌函数的闭包 (闭包创建的闭包)
同一闭包创建的其他闭包共享同一份upvalue
、
先上代码
function newCounter()
local count = 0
function f0()
function f1()
count = count + 1
return count
end
function f2()
print(count)
end
return f1,f2
end
return f0
end
t = newCounter()
g1 , g2 = t()
g1()
g2() --> 1
g3 , g4 = t()
g3()
g4() --> 2
这里我们的g1 g2 g3 g4
创建的时候,count
已经结束生命周期了。所以创建时堆栈上根本找不到变量count
。此时他们便到他们的外包的闭包 t
中寻找。此时t g1 g2 g3 g4
共享count
。
网友评论