Lua 中的函数是第一类值。意味着和其他的常见类型的值(例如数值和字符串)具有同等权限。
举个例子,函数也可以像其他类型一样存储起来,然后调用
-- 将 a.p 指向 print 函数
a = { p = print }
-- 使用 a.p 函数
a.p("jiangpengyong") ---> jiangpengyong
正常情况下,我们定义一个函数是下面代码这样的
function foo1()
print("Foo1 called.")
end
然而,其实可以写成下面这样,将函数赋值给一个变量,这样 foo 就是一个函数类型的变量了。
foo = function(x)
return x * x
end
其实 Lua 的函数就是一个 function
类型的变量(可以查看之前的文章“Lua 数据类型 —— 函数”)。第一种方式变量名即为函数名(foo1),第二种则为变量名(foo)
因为函数是一个变量,所以也可以进行判断类型,删除变量等操作。
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 函数作为第一类值特性的一个表现,并不是新的特性。
举个例子:
这里编写一个导数函数
f`(x) = (f(x + d) - f(x))/d
编写如下,f
即使一个函数
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
因为函数在 Lua 中与其他类型具有同等权限,所以也可以 table 中定义。
第一种方式,用了匿名函数进行定义,只是归属至表
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
第二种方式,也可以使用表构造器的一种方式(记录式)创建
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
第三种方式,只是用了常规的函数定义
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
即可
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')
错误。
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'
。
所以可以先进行声明然后在使用,就可以避免这一问题。
local fact3
fact3 = function(n)
if n == 0 then
return 1
end
return n * fact3(n - 1)
end
print(fact3(10)) --> 3628800
所以如果涉及到递归,或者是间接递归,可以考虑先将函数变量声明,然后再进行赋值。
可能会有疑惑,为什么第一种方式就没有问题?
其实只是 Lua 语言帮我们展开了
local function foo(n) body end
-- Lua 帮我们展开为以下代码
local foo;
foo = function (n) body end
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 函数的参数从原来的 弧度 单位改为 角度 单位。
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吧,码字不易,请多多支持)
本章相关代码传送门
如果觉得本篇博文对你有所启发或是解决了困惑,点个赞或关注我呀,后续会分享更多的优质文章。