Lua的函数

前言:
  最近在学些Lua的函数,之前学的太快导致有些概念性的东西记不住,这次把这些东西梳理下串起来。

1 Lua函数设计到的几个概念

  问题:一些Lua书籍会这样介绍Lua中的函数,“Lua中的函数是带有词法定界的第一类值。你可能被里面的名词搞蒙了,我们看下这两个术语到底什么什么意思。

  • 第一类值:我们都知道变量可以存储值,可以作为函数参数输入,可以作为函数返回值输出,可以存放在数组里,我们把这种特性称为第一类值。而Lua把函数也当作一种值来处理,在Lua你可以把函数当作值在变量中赋值传递,函数可以作为其他函数的参数(匿名函数比较喜欢这么用),可以作为函数的返回值输出(Lua实现闭包基础),可以存放在表中(Lua实现对象的基础)。因此我们说Lua的函数是第一类值。
  • 词法定界:Lua函数可以嵌套定义。我们知道Lua的变量有两种,一种是定义在函数外部的全局变量,一种是定义在函数内部的局部变量。函数可以访问全局变量,但函数不能访问其他函数的局部变量,但有一种情况除外,就是内部嵌套定义的函数(注意这里说的是内部嵌套定义的函数,而不是内部调用的函数)可以访问外层函数的局部变量(upvalue),这种特性称为词法定界。不要把词法定界概念等同于闭包概念。
1
2
3
4
5
6
7
8
9
10
11
12
13
function add1(a, b, c)
d = 1

function add2()
return a + b + c + d
end

ret = add2()

return ret
end

print(add1(1, 2, 3))

  运行输出结果为:7
  这段代码演示了在add1函数内嵌套定义add2函数,add2函数本身参数为空,但直接调用了外部函数的局部变量。

  • 闭包:Lua闭包可以说就是一个内部函数结合对upvalue变量的操作。Lua闭包结构特征明显,由两个函数构成,一个是内部函数自己,另一个是外部函数(称为工厂)。所以说Lua闭包并不是指某一种特殊的函数,而是我们所熟知的普通函数按照一定规则组合而成,函数之间存在某种特性(内部函数可访问外部函数局部变量,具有保存历史状态的特点)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function Test()
local i = 0
local j = 0
local k = 0

j = j + 1
k = k + 1

return function()
i = i + 1
k = k
return i, j, k
end
end

--fun1和fun2是两个不同的实例
fun1 = Test()

print("fun1--", fun1())
print("fun1--", fun1())
print("fun1--", fun1())
print("fun1--", fun1())

fun2 = Test()

print("fun2--", fun2())
print("fun2--", fun2())
print("fun2--", fun2())
print("fun2--", fun2())

  运行输出结果为:

1
2
3
4
5
6
7
8
fun1--  1       1       1
fun1-- 2 1 1
fun1-- 3 1 1
fun1-- 4 1 1
fun2-- 1 1 1
fun2-- 2 1 1
fun2-- 3 1 1
fun2-- 4 1 1

  这里在内部匿名函数里操作外部变量i,当一次调用结束后再次调用,i的值是在第一次调用的基础上累加,而外部函数里操作j和k值与之前是否调用过无关。也即闭包结构下内部函数使用外部函数的局部变量,具有记住历史状态的特性(这个特性可以设计迭代器)。虽然fun1和fun2的函数原型都是Test,但是他们有各自所操作的函数实例。fun1的调用次数与fun2调用次数互不影响。
  继续看例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Test()
local i = 0
local k = 0

k = 1

return function()
i = i + 1

if i >= 3 then
k = k + 1
end

return i, k
end
end

fun1 = Test()

print("fun1--", fun1())
print("fun1--", fun1())
print("fun1--", fun1())
print("fun1--", fun1())

  运行输出结果为:

1
2
3
4
5
6
7
8
fun1--  1       1
fun1-- 2 1
fun1-- 3 2
fun1-- 4 3
fun2-- 1 1
fun2-- 2 1
fun2-- 3 2
fun2-- 4 3

  这里例子里外部函数每次调用都会给k值赋值为1,而内部函数里进行累加1。从输出结果看出,k值的确是从1开始累加的,但在第一调用fun1创建k之后,闭包已经开始记住它的历史值了(有点类似c语言的static变量,但还是有本质区别),随后的调用累加都是在历史值基础上累加而不受外部函数赋值影响。假如你想在k累加到10之后不再累加,条件判断必须写在内部函数里,写在外部函数是无效的,外部函数只能决定k的最初历史值。

  • 闭包在哪些方面使用
    • 既然具有保留历史状态的特点当然适合用作迭代器。
    • 同一个函数原型可以运行不同的函数实例,比如你有一个函数用迭代特性不断从一个状态推导出下一个状态(可能状态比较复杂,只能从历史状态推出下一个状态,不能像sin函数似的给x直接算y),然后原来的推导想继续,又期望开始一个新的推导,两个推导初始参数不同规则相同。