Lua的Upvalue和闭包(一)
upvalue
什么是upvalue
Lua的upvalue指的是函数内引用的非全局的外部变量,这么说有两层意思:
1)他是非全局的变量,即这个变量是用local修饰的
2)它是外部变量,即这个变量不是在函数内定义的变量
如下代码所示:
local test_var_1 local test_var_3 test_var_2 = nil function foo() local test_var_4 = 0 test_var_1 =1 test_var_2 = 3 test_var_1 = test_var_1+1 end
在上述例子中:
1)test_var_2是全局变量,所以不是foo的upvalue
2)test_var_3是局部变量,但是函数foo中并没有引用,所以test_var_3不是foo的upvalue
3)test_var_1是局部变量,且被函数foo引用,所以test_var_1是upvalue
4)test_var_4是函数内定义的局部变量,所以test_var_4不是upvalue
在Lua中function也是first class类型,这意味着upvalue不仅是数值型或者字符串类型变量,也有可能类型是function类型变量,如下:
function zoo() print("zoo") end local function bar() print("bar") end local function foo() bar() zoo() print("foo") end
上述代码中:
1)函数foo引用局部方法bar,所以bar是foo的upvalue
2)函数foo也引用了方法zoo,但是由于zoo是全局方法,所以zoo不是bar的upvalue
什么是闭包
在Lua中闭包分为C-Clousure和Lua-Closure,这里介绍Lua-Closure:Lua中闭包是一个运行时的概念,我们称一个函数及其访问的upvalue构成一个闭包,如下示例所示:
c=3 local function foo() print("foo") end function test() local a= 1 local b = 2 function num() a=a+1 b=b+3 c=c+5 foo() print(a,b,c) end return num end local l1 = test() l1()
l1就是一个闭包,且里面含有四个upvalue:a、b、_ENV和foo;通过闭包:
1)可以实现类似C++静态变量的效果
2)也可以实现接口执行状态的保留
闭包和upvalue的接口
Lua提供了一套upvalue相关的接口,都在debug这个table下,下面逐一介绍及演示用法:
getupvalue
用于获取指定闭包指定位置的upvalue的key及其值(非值类型则返回地址),如下:
local a = 1 local b = 2 c=3 local function foo() print("foo") end function zoo() print("zoo") end local function test() local function bar() a=a+1 b=b+1 foo() zoo() end return bar end clo=test() local i=1 while true do local key,val = debug.getupvalue(clo,i) i=i+1 if key==nil or val==nil then break end print(key,val) end
clo用于接收test方法返回的闭包,其后在循环代码内:
1)通过getupvalue用于获取指定位置的upvalue,若指定位置的upvalue不存在则返回nil
2)getupvalue返回两个值,一个是upvalue的key,另外一个是upvalue的值/地址
执行结果如下:
通过执行的结果我们可以看到:
1)当闭包引用有全局变量或者方法的时候,会将全局表_ENV添加到闭包的upvalue列表
2)upvalue在闭包的upvalue的列表排列的顺序是按照引用的顺序来的
但是对于loader来说,一旦引用全局变量,默认有upvalue列表,且第一个位置为_ENV,代码如下:
local search = package.searchers[2] local loader,path = search("test/test_upval") print(loader,path) if loader ~= nil then local i=1 while true do local k,v = debug.getupvalue(loader,i) if k~=nil or v~=nil then print(k,v) end i=1+i end end
执行结果如下:
upvalueid
upvalueid原型为debug.upvalueid(f,n),用于获取指定函数发,第n个upvalue的唯一标识符,通过这个upvalue我们可以看到哪些upvalue是多个闭包对象共享的,代码如下:
local a= 1 local b = 2 c=3 function M:foo() print("foo") end function zoo() print("zoo") end function M:test() local c=1 function num() a=a+1 b=b+3 c=c+5 M:foo() print(a,b,c) end return num end local l1 = M:test() print(debug.upvalueid(l1,3),debug.upvalueid(l1,2)) local l2 = M:test() print(debug.upvalueid(l2,3),debug.upvalueid(l2,2))
执行结果为:
从执行结果可以看到:
1)闭包的upvalueid是userdata类型
2)upvalue的共享范围取决于作用域的范围,因此a、b定义于最外层局部变量,则其值被多个闭包对象共享并修改;c则作用域之内缘故,闭包对象共享对应对象
实际上upvalueid实际返回的是upvalue的地址,因此不同的upvalue的id是保证全局唯一的。
setupvalue
setupvalue的原型为debug.setupvalue(f,up,value)
用于将函数f指定下标为up的upvalue的值改为value,这个upvalue可以是值类型也可以是函数类型,但是对于function类型和值类型还是有区别的,示例代码如下:
local M={} local c=9 local function foo() print("foo") end local function bar() print("bar") end function test_upvalue() local a = 1 local b = 2 function test_setupvalue() a = a+1 b = b+2 bar() print(a,b) end return test_setupvalue end local tmp1 = test_upvalue() local tmp2 = test_upvalue() tmp1() tmp2() debug.setupvalue(tmp1,3,foo) debug.setupvalue(tmp1,1,c) tmp1() tmp2()
从这段代码中:
1)闭包中upvalue列表在数组中排列顺序是按照首次引用顺序来的
2)示例中,通过debug.setupvalue
分别修改闭包中指定upvalue的引用地址
3)从执行结果来看:
对于值类型来说:修改upvalue不会影响已创建的闭包,即修改upvalue a的值,不影响tmp2的upvalue;
对于表类型/方法类型来说:若是修改tmp1的upvalue的function类型bar的值,会影响到tmp2的。
执行结果如下图所示:
upvaluejoin
debug.upvaluejoin(f1,n1,f2,n2)
用于将闭包f1中第n1个upvalue和f2中下标为n2的upvalue关联起来,示例代码如下:
c=3 local function foo() print("foo") end local function zoo() print("zoo") end function test() local a= 1 local b = 2 function num() a=a+1 b=b+3 c=c+5 foo() print(a,b,c) end return num end local l1 = test() l1() l1() l1() local l2 = test() l2() --l2() --print(debug.setupvalue(l1,1,l2,1)) debug.upvaluejoin(l1,1,l2,1) l1() l2()
示例代码创建两个闭包l1和l2,并通过接口setupvalue
将l1的第一个upvalue去引用l2的第一个upvalue,词首l1的a的修改会同步给l2的a,反之亦然,执行效果如下:
上述代码中,先是创建闭包l1和l2,然后分别调用使得闭包内的状态发生变化,然后通过接口upvalue.join使得l1.a指向l2.a,此后l2和l1对a的修改是共享的。
除了值类型的upvalue可以被替换关联,function类型也可以,如下
c=3 local function foo() print("foo") end local function zoo() print("zoo") end function test() local a= 1 local b = 2 function num() a=a+1 b=b+3 c=c+5 foo() print(a,b,c) end return num end function test2() local m1=300 local m2=400 function num1() m1=m1+1 m2=m2+10 zoo() print(m1,m2) end return num1 end local l1 = test() local l2 = test2() l1() l2() debug.upvaluejoin(l1,4,l2,3) l1()
上述代码中,闭包l1含有四个upvalue,依次是:a、b、_ENV(因为第三个外部遍历是全局变量c,所以用的upvalue是全局表)、foo;而闭包l2的三个upvalue,依次是:m1、m2、zoo;通过接口upvaluejoin实现l1第一个upvalue引用l2的第三个upvalue,即闭包l1调用的接口foo替换为test2里面的zoo,执行效果如下:
以上为Lua闭包和upvalue的概念层的介绍,通过本文知道Lua的闭包概念以及相关的用法,为加深理解,我们将简单梳理Lua闭包和upvalue的源码,看下底层是怎么对闭包的逻辑和upvalue进行组织的。
参考资料
https://blog.csdn.net/sbddbfm/article/details/94424695
https://blog.csdn.net/zxm342698145/article/details/79710179
https://zhuanlan.zhihu.com/p/358423900