Lua 闭包

一、Lua 中的函数

Lua 中的函数是第一类值。意味着和其他的常见类型的值(例如数值和字符串)具有同等权限。

举个例子,函数也可以像其他类型一样存储起来,然后调用

lua 复制代码
-- 将 a.p 指向 print 函数
a = { p = print }
-- 使用 a.p 函数
a.p("jiangpengyong")    ---> jiangpengyong

二、匿名函数

正常情况下,我们定义一个函数是下面代码这样的

lua 复制代码
function foo1()
    print("Foo1 called.")
end

然而,其实可以写成下面这样,将函数赋值给一个变量,这样 foo 就是一个函数类型的变量了。

lua 复制代码
foo = function(x)
    return x * x
end

其实 Lua 的函数就是一个 function 类型的变量(可以查看之前的文章"Lua 数据类型 ------ 函数")。第一种方式变量名即为函数名(foo1),第二种则为变量名(foo)

因为函数是一个变量,所以也可以进行判断类型,删除变量等操作。

lua 复制代码
foo = function(x)
    return x * x
end
print(foo(2))					-->	4
print("type(foo)", type(foo))	-->	type(foo)	function

function foo1()
    print("Foo1 called.")
end
foo1()							--> Foo1 called.
print("type(foo1)", type(foo1))	--> type(foo1)	function
foo1 = nil
--foo1()    -- attempt to call a nil value (global 'foo1')
print("type(foo1)", type(foo1))	--> type(foo1)	nil

三、高阶函数

以另一个函数作为参数的函数,即为高阶函数。

其实这只是 Lua 函数作为第一类值特性的一个表现,并不是新的特性。

举个例子:

这里编写一个导数函数

lua 复制代码
f`(x) = (f(x + d) - f(x))/d

编写如下,f 即使一个函数

lua 复制代码
function derivative(f, delta)
    delta = delta or 1e-4
    return function(x)
        return (f(x + delta) - f(x)) / delta
    end
end
c = derivative(math.sin)
print(math.cos(5.2), c(5.2))    --> 0.46851667130038	0.46856084325086

四、在 table 中定义函数

因为函数在 Lua 中与其他类型具有同等权限,所以也可以 table 中定义。

第一种方式,用了匿名函数进行定义,只是归属至表

lua 复制代码
Lib1 = {}
Lib1.add = function(a, b)
    return a + b
end
Lib1.reduce = function(a, b)
    return a - b
end
print("Lib1", Lib1.add(10, 2), Lib1.reduce(2, 3))   --> Lib1	12	-1

第二种方式,也可以使用表构造器的一种方式(记录式)创建

lua 复制代码
Lib2 = {
    add = function(a, b)
        return a + b;
    end,
    reduce = function(a, b)
        return a - b;
    end
}
print("Lib2", Lib2.add(10, 2), Lib2.reduce(2, 3))   --> Lib2	12	-1

第三种方式,只是用了常规的函数定义

lua 复制代码
Lib3 = {}
function Lib3.add(a, b)
    return a + b
end
function Lib3.reduce(a, b)
    return a - b
end
print("Lib3", Lib3.add(10, 2), Lib3.reduce(2, 3))   --> Lib3	12	-1

五、非全局函数

定义一个局部函数和定义一个局部变量是一样的,例如下面的代码,只需要加上 local 即可

lua 复制代码
local function fact1(n)
    if n == 0 then
        return 1
    end
    return n * fact1(n - 1)
end
print(fact1(10))    --> 3628800

值得注意

如果用匿名函数定义局部函数的话,则会有坑。

当定义一个递归函数,例如下面这段代码,运行起来会报 attempt to call a nil value (global 'fact2') 错误。

lua 复制代码
local fact2 = function(n)
    if n == 0 then
        return 1
    end
    -- 因为 Lua 语言编译函数体中的 fact2(n-1) 调用时,局部的 fact2 尚未定义。
    return n * fact2(n - 1)    -- attempt to call a nil value (global 'fact2')
end
print(fact2(10))

这是因为 Lua 语言编译函数体中的 fact2(n-1) 调用时,局部的 fact2 还未定义,所以会在全局中进行搜索,所以报错中提示的是 global 'fact2'

所以可以先进行声明然后在使用,就可以避免这一问题。

lua 复制代码
local fact3
fact3 = function(n)
    if n == 0 then
        return 1
    end
    return n * fact3(n - 1)
end
print(fact3(10))    --> 3628800

所以如果涉及到递归,或者是间接递归,可以考虑先将函数变量声明,然后再进行赋值。

吾有一惑

可能会有疑惑,为什么第一种方式就没有问题?

其实只是 Lua 语言帮我们展开了

lua 复制代码
local function foo(n) body end

-- Lua 帮我们展开为以下代码

local foo;
foo = function (n) body end

六、作用域外溢

lua 复制代码
function newCounter()
    local count = 0
    return function()
        count = count + 1
        return count
    end
end

local c1 = newCounter()
print("c1", c1())       --> c1  1
print("c1", c1())       --> c1  2

local c2 = newCounter()
print("c2", c2())       --> c2  1
print("c1", c1())       --> c1  3
print("c2", c2())       --> c2  2
print("c2", c2())       --> c2  3

通过 newCounter 返回一个匿名函数,达到能够 "访问" count , 这就是作用域外溢。

count 的作用域是 newCounter 函数,但是因为作为匿名函数返回,所以外溢至外部。而且每次调用的 local 都不一样。

七、更换预定义函数

Lua 中可以给一个变量重新定义一个新的函数,也可以给一个预定义函数重新定义函数。

例如,我们可以将 sin 函数的参数从原来的 弧度 单位改为 角度 单位。

lua 复制代码
print("更换预定义函数:")
--- rad 将角度转为弧度
print("更换前,使用弧度制", math.sin(math.rad(90)))     --> 更换前,使用弧度制	1.0
do
    local oldSin = math.sin
    math.sin = function(value)
        return oldSin(value * (math.pi / 180))
    end
end
print("更换后,使用角度", math.sin(90))                 --> 更换后,使用角度	1.0

使用 do-end 则将 oldSin 的作用域限制起来了,后续的调用只能调用到替换的函数

拓展一下

可以利用这种特性,在原有的函数中增加一些项目所需要的代码,例如日志输出,文件检测等。

八、写在最后

Lua 项目地址:Github传送门 (如果对你有所帮助或喜欢的话,赏个star吧,码字不易,请多多支持)

本章相关代码传送门

如果觉得本篇博文对你有所启发或是解决了困惑,点个赞或关注我呀,后续会分享更多的优质文章。

相关推荐
ejinxian2 分钟前
PHP 超文本预处理器 发布 8.5 版本
开发语言·php
福柯柯8 分钟前
Android ContentProvider的使用
android·contenprovider
不想迷路的小男孩9 分钟前
Android Studio 中Palette跟Component Tree面板消失怎么恢复正常
android·ide·android studio
餐桌上的王子10 分钟前
Android 构建可管理生命周期的应用(一)
android
菠萝加点糖14 分钟前
Android Camera2 + OpenGL离屏渲染示例
android·opengl·camera
GiraKoo24 分钟前
【GiraKoo】C++14的新特性
c++
用户20187928316725 分钟前
🌟 童话:四大Context徽章诞生记
android
软件黑马王子28 分钟前
C#系统学习第八章——字符串
开发语言·学习·c#
阿蒙Amon30 分钟前
C#读写文件:多种方式详解
开发语言·数据库·c#
yzpyzp33 分钟前
Android studio在点击运行按钮时执行过程中输出的compileDebugKotlin 这个任务是由gradle执行的吗
android·gradle·android studio