Lua的Upvalue和闭包(一)

ronald8个月前职场1970

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的值/地址

执行结果如下:

image-20220122221510331.png

通过执行的结果我们可以看到:

    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

执行结果如下:

image-20220122231924729.png

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))

执行结果为:

image-20220123000452304.png

从执行结果可以看到:

    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的。

执行结果如下图所示:

image-20220123100851077.png

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,反之亦然,执行效果如下:

image-20220123233216276.png

    上述代码中,先是创建闭包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,执行效果如下:

企业微信截图_16429913992527.png

    以上为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

    Lua细节归纳 - zhyingkun

相关文章

Lua的Upvalue和闭包(二)

Lua的Upvalue和闭包(二)

Lua闭包和Upvalue的实现    前面文章介绍了Lua闭包和upvalue的概念,本文简单过一下Lua对于闭包和upvalue的实现以加深理解。Lua闭包结构    Lua在内存的结构如下所示:#define ClosureHeader \ CommonHeader; lu_by...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。