Lua闭包和Upvalue上值

简介: Lua闭包和Upvalue上值

一、lua中的作用域

在Lua语言中声明的变量默认是全局变量,声明局部变量需要使用local关键字,和其他语言相比这有点特殊。

-- 全局变量
a = 10
function func()
    b = 100   -- 仍然是全局变量
    local c = 20  -- func的局部变量
end
func()
print(a + b)  -- 输出 110
print(c)    -- 输出 nil

运行结果:

二、什么是闭包

我们知道,函数里的本地变量只能在函数内部访问,函数退出之后,作用域就没用了,它对应的栈桢被弹出,作用域中的所有变量所占用的内存也会被收回。

闭包的功能是让一个函数的变量逃不出去,什么意思?意思是当一个函数被当做成是一个返回值时,这个函数里所有引用的变量都不会丢失,不会因为离开了一个变量离开了作用域而导致创建或者使用这个函数时,其变量值会发生变化。

当一个函数内部嵌套另一个函数定义时,内部的函数可以访问外部的函数的局部变量,这种特征在lua中叫:词法定界

lambda表达式就是经典的闭包方式之一。

简单来说,闭包就是能够读取其他函数内部变量的函数。

三、闭包的实现

要实现闭包,需要解决两个问题:

函数是第一类值(first class value),也就是要能把函数像普通数值一样赋值给变量,可以作为参数传递给其他函数,可以作为函数的返回值。

满足词法作用域(Lexical Scope),要让内层函数能够访问外层函数中的变量,不管外层函数退出与否。

在Lua语言中,函数是严格遵循词法作用域的第一类值。具备了实现闭包的条件,下面通过一个例子说明什么是闭包。

例子中,定义了一个全局变量a,和func函数,并且在func函数内部定义了局部变量b。func函数中内嵌并返回了一个test函数,test返回的是全局变量和func的局部变量,我们来看看程序输出结果:

a = 10
function func()
    local b = 20;
    function test()
        a = a + 10;
        b = b + 20;
        -- 返回全局变量a和局部变量b
        return a, b
    end
    return test
end
-- tmpFunc1 就是一个闭包
tmpFunc1 = func()
for i=1, 3 do
    v1, v2 = tmpFunc1()
    print(v1, v2)
end

运行结果:

分析:

内层函数test作为返回值赋值给tmpFunc1 变量后,外层函数func就结束了,但内层函数仍能访问原来外层函数的变量 b,当然,也能访问全局变量 a。

站在func的角度来看,自己已经退出了,那么自己定义的局部变量b就应该失效了,但是,闭包tmpFunc1仍然可以访问,这就是闭包。

四、Upvalue 上值

内嵌函数可以访问外包函数已经创建的局部变量,而这些局部变量则称为该内嵌函数的upvalue,也就是上值。

对于上面的例子,局部变量b就是内嵌函数test的上值。注意,函数的参数也是局部变量,看下面的例子:

function create_func(n)
    function test()
        n = n + 100;
        return n
    end
    return test
end
func1 = create_func(1000)
print(func1())
func2 = create_func(50)
print(func2())

运行结果

分析:

当 func1 = create_func(1000) 这行代码执行完,局部变量n的生命周期本该结束,但是,因为n已经成为了内嵌函数test的上值,以某种方式“存活”在闭包func1中。

4.1 Upvalue为闭包提供数据共享
4.1.1 函数创建的闭包

一个函数创建的闭包共享一份upvalue。

function create_func(n)
    function test1()
        n = n + 100;
        print(n)
    end
    function test2()
        print(n)
    end
    return test1, test2
end
func1,func2 = create_func(1000)
func1()
func2()
print("")
func1()
func2()

运行结果:

结果分析

func1 和 func2 这两个闭包的函数原型分别是 create_func 中的内嵌函数test1和test2,而 test1 和 test2 引用的upvalue是同一个,即create_func 的形参n(局部变量)。

执行完create_func调用后,Lua发现这两个闭包的upvalue指向的是当前堆栈上的相同变量时,只生成一个拷贝,然后让这两个闭包共享该拷贝,这样任一个闭包对该upvalue进行修改都会被另一个知悉。

可以看到,每次调用 func1都将upvalue的值增加了100,随后func2将更新后的值打印出来。upvalue的这种语义很有价值,它使得闭包之间可以不依赖全局变量进行通讯,从而使代码的可靠性大大提高。

4.1.2 闭包创建的闭包(多重内嵌)
function create_func(n)
    function test ()
        function test1()
            n = n + 100;
            print(n)
        end
        function test2()
            print(n)
        end
        return test1, test2
    end
    return test
end
func = create_func(1000)
func1, func2 = func()
func1()
func2()
print("")
func1()
func2()
print("====================")
func3, func4 = func()
func3()
func4()
print("")
func3()
func4()

运行结果:

结果分析

在执行完 func = create_func(1000) ,create_func 的局部变量 n 的生命周期就结束了,所以当func1,func2这两个闭包被创建时堆栈上根本找不到变量 n。create_func 函数的局部变量n不仅是 test 的 upvalue,也是test1和test2的upvalue。闭包 func 已经把n保存为upvalue,之后func1、func2如果在当前堆栈上找不到变量n就会自动到它们的外包闭包(这里指的是闭包func)的upvalue引用数组中去找.


func3和func4与func和func2共享同一个上值upvalue。因为func3和func4与func和func2都是同一个闭包 func 创建的,所以它们引用的upvalue (变量n)实际也是同一个变量,而它们的upvalue引用都会指向同一个地方。


推荐一个零声学院免费教程,个人觉得老师讲得不错,分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,点击立即学习:

相关文章
|
XML 自然语言处理 Java
【Lua基础 第5章 】unpack()和pack()、Lua 中的文件 I/O、简单模式下io的部分方法、完全模式下file的部分方法、日期和时间、闭包使用
unpack()和pack()、Lua 中的文件 I/O、简单模式下io的部分方法、完全模式下file的部分方法、日期和时间、闭包使用
627 0
【Lua基础 第5章 】unpack()和pack()、Lua 中的文件 I/O、简单模式下io的部分方法、完全模式下file的部分方法、日期和时间、闭包使用
|
机器学习/深度学习 C++
在C++使用LUA交互,LUA实现闭包,C++/LUA相互闭包
LUA可谓是配置文件神器,具体功能用过才知道,接近两年没用了抽了俩小时熟悉了下基本的用法。 包括C/LUA堆栈操作 函数相互调用 以及LUA的闭包 C++和LUA相互闭包 想要灵活使用LUA必须先要学习 LUA和C的堆栈交互模型 类似于汇编函数调用方式了 很有意思。
1013 0
|
C++
[Lua]lua闭包
前言 在很多语言中都有闭包的概念,而在这里,我将主要对Lua语言的闭包概念进行分析与总结。希望对大家学习Lua有帮助。 什么是闭包? 闭包在Lua中是一个非常重要的概念,闭包是由函数和与其相关的引用环境组合而成的实体。
1161 0
|
4月前
|
存储 NoSQL 关系型数据库
使用lua脚本操作redis
使用lua脚本操作redis
50 0
|
4月前
|
NoSQL Java Redis
Redis进阶-lua脚本
Redis进阶-lua脚本
62 0
|
2月前
|
缓存 NoSQL Java
【Redis】5、Redis 的分布式锁、Lua 脚本保证 Redis 命令的原子性
【Redis】5、Redis 的分布式锁、Lua 脚本保证 Redis 命令的原子性
63 0
|
3月前
|
算法 NoSQL Java
springboot整合redis及lua脚本实现接口限流
springboot整合redis及lua脚本实现接口限流
82 0
|
7天前
|
存储 NoSQL 调度
Redis Lua脚本:原子性的真相揭秘
【4月更文挑战第20天】
23 0
Redis Lua脚本:原子性的真相揭秘