• 欢迎大家分享资料!前往留言板评论即可!

Lua 函数闭包那些事

合宙 模组资料网 2年前 (2021-05-15) 435次浏览 0个评论 扫描二维码

在 lua 中函数属于第一类值, 也就是说, 函数与数值, 字符串等, 都属于同等类型的值. 同时, lua 的函数也是匿名的, 函数没有名称可言, 虽然听起来有些怪, 但是在接受了函数属于第一类值这个观点后, 这种推论其实也是顺理成章的.

name = "Alice"
age = 17

你可以说 name 中存储的值是 “Alice” , 也可以说, age 中存储的值是17, 却不可能说 “Alice” 的名称是 name, 值都是匿名的, 同样作为函数来讲:

function foo()
-- something
end

虽然用 foo 这个名称声明了函数, 但这实际上也只是 lua 的一个语法糖而已

foo = function()
-- somthing
end

foo 和这个函数之间并不存在特定的占有关系, 两种实现完全没有什么区别, 所以在 lua 中, 可以像交换两个变量一样交换两个函数的实现.

作为第一类值, lua 的函数可以作为表的键和值, 由于 lua 本身没有提供 switch 语句, 所以用函数当值是 lua 中很经典的一种替代 switch 的方案, 省去了大段 if-else 的比较

tab = {
    foo = function() 
        print("foo") 
    end,
    bar = function() 
        print("bar") 
    end,
}
if val and tab[val] then
    tab[val]()
end

lua 的函数可以被当做函数的输入参数, 也可以被创建在函数的内部, 或者被当成函数的返回值

function foo()
    function bar() -- 内部创建

    end
    return function () end -- 返回值
end
foo(function() end) -- 参数

像这种在函数内创建的函数被称为内嵌函数, 内嵌函数会首先访问外部函数的局部变量

n = 10
function foo()
    local n = 20
    function bar()
        print(n)
    end
    bar()
end
foo() -- 20

需要注意的是, 这种访问遵循词法定界, 也就是静态定界, 这种定界在进行词法分析时就能够确定, 简单来说, 就是一个 function()…end 在另一个 function()…end 内部, 仅凭肉眼就能定界, 不需要大脑思考, 不用考虑调用栈上的参数情况. 像下面这种情况, 尽管也是在函数内部创建函数, 但是显然不符合词法定界, 因此无法访问 foo 的局部变量

n = 10
function func()
    return function()       
        print(n)    
    end
end

function foo()
    local n = 20
    bar = func()
    bar()
end

foo() -- 10

我们上面考虑的情况都是外部函数的局部变量都还活跃于栈中, 因为 lua 的函数可以作为返回值, 所以会出现下面这种情况:

function func()
    local n = 20
    return function()
        print(n)
    end
end
foo = func()
foo()

通过运行会发现, 最后结果会打印20, 但是仔细思考的话就会发现问题所在, func 函数执行完毕后, 局部变量 n 也会随之被销毁, 那此时 foo 函数打印的是哪里的变量值? 按照正常情况来说, 这个值是无法打印成功的, 但是 lua 通过闭包机制实现了这个值的正常打印. 其实严格来讲, Lua 并没有函数, 有的只是闭包, 闭包和函数的关系是这样的:

闭包 = 函数 + upvalue

当一个闭包没有 upvalue 的时候, 可以认为这就是一个函数, 当然也可以反过来理解, 一个函数有了 upvalue 就是闭包, 可以把 upvalue 当成是函数的一个隐藏空间. 在不引起歧义的情况下, 我们可以把这样的闭包称之为函数.
当内嵌函数访问外部函数的局部变量时, 实际上是通过 upvalue 访问的, 此时 upvalue 相当于一个指针, 指向被引用的变量, 当被引用的变量被销毁时, lua 就会把这个被引用的值拷贝进函数的隐藏空间, 此时的这个 upvalue (lua 的函数可以有多个 upvalue)就会从 open 状态变为 close 状态, 这时候的函数就可以做到自食其力, 成为一个真正的闭包.

下面就是闭包的典型用法:

function func(n)
    return function()
        print(n)
        n = n + 1
    end
end

f1 = func(1)
f2 = func(1)
f1() -- 1
f1() -- 2
f2() -- 1
f2() -- 2

如果理解了上面的用法, 我们接下来再看一个例子

function func(n) -- 函数2
    return function() -- 函数1
        return function()
            print(n)
            n = n + 1
        end
    end
end

f = func(1)
f3 = f()
f4 = f()
f3() -- 1
f3() -- 2
f4() -- 3
f4() -- 4

这次 f3 和 f4 共享一个 upvalue, 与之前 f1 和 f2 的区别在于, f1 和 f2 的闭包是先通过引用局部变量, 然后局部变量失联形成的闭包, 外部函数两次调用生成了两个局部变量, 所以生成的两个闭包是相互独立的. f3 和 f4 的情况则有所不同, f3 被调用时找不到变量 n, 会向外层函数1查找, 外层函数1没找到会向外层函数2发起查找, 所以当函数1返回时, n 会成为函数1的 upvalue. f3 直接引用的是函数1的闭包的upvalue, f4 也是引用的也是函数1的闭包的upvalue. 所以 f3 和 f4 才会呈现出关联性.


转载请注明原文链接:Lua 函数闭包那些事
喜欢 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址