函数是值类型
《programming in lua》里面举了一个非常生动的例子:
a = {p = print}
a.p("Hello World") --> Hello World
print = math.sin -- `print' now refers to the sine function
a.p(print(1)) --> 0.841470
sin = a.p -- `sin' now refers to the print function
sin(10, 20) --> 10 20
意味着当我们把函数变量指向另一个函数的时候,原地址依然保存着那份函数,可以用其他变量名指向这个函数,并接着调用。
高级函数
table.sort实际上就是一个典型的高级函数,其第二个参数即是function类型的,用于确定两个值哪个值应当在前:
table.sort(list,function(a,b)return a > b
end)
词法定界&闭包
这是我认为lua最灵活强大的功能之一。
词法定界的意思即是,局部函数内部可以访问局部函数外部的变量:
bAscending = true
a = {2,3,1,6,7,8}
table.sort(a,function(num1,num2)if bAscending thenreturn num1 < num2elsereturn num1 > num2end
end)
for i = 1,#a doprint(a[i])
end
如上,bAscending可以在比较函数外面被访问到。
官方文档中一个更有意思的示例代码,我修改了一点以更清晰:
function newCounter() local i = 0 local f = function() -- anonymous function i = i + 1return iendprint(f)return f
end
c1 = newCounter() --> function: 000001A8D24EB820
print(c1()) --> 1
print(c1()) --> 2
c2 = newCounter() --> function: 000001A8D24EBAC0
print(c2()) --> 1
print(c1()) --> 3
print(c2()) --> 2
再略微修改上方的一些代码,观察输出:
function newCounter() local i = 0 local f = function() -- anonymous function i = i + 1return iendprint(f)return f
end
c1 = newCounter -->
print(c1()) --> function: 000001D128558270 function: 000001D128558270
print(c1()) --> function: 000001D128558840 function: 000001D128558840
c2 = newCounter -->
print(c2()) --> function: 000001D1285583F0 function: 000001D1285583F0
print(c1()) --> function: 000001D128558600 function: 000001D128558600
print(c2()) --> function: 000001D1285582A0 function: 000001D1285582A0
发现不一样的地方了吗?
有几点我们需要额外注意:
- 第一段代码的c1和c2最终其实都是闭包函数本身,在初始化的第一时间,就执行了print(f)的操作,自此之后,c1和c2就是f,所以print(c1())实际就是print(f())
- c1和c2初始化时的f相当于是开创的新变量,其指向的是全新的值,所以可以看到初始化时候的print(f)显示的地址是新地址,而且执行c1()和c2()的计数是完全独立,不受影响的
- 第二段代码的c1和c2实际是newCounter函数,所以第一时间没有输出值,而执行print(c1()) or print(c2())函数时print了两个完全相同的地址,其实也很好理解,相当于每次执行newCounter,其内部print了一次闭包函数地址,返回的又是此函数的地址,所以print了两次一样的值。
- 第二段代码每一次print出来的function地址都不一致,意味着每次执行这个函数,闭包函数地址都会变,进一步解释了第二条的计数完全独立的原因。
尾调用
很简单的一个概念,即return一个新函数时,不开辟额外栈空间去单独执行,而更类似于goto的状态转换。计算机基础里面有提到过,函数内部调用函数相当于开辟一个栈空间去执行,以此类推,直到一层层返回值即一层层清除栈空间,所以会有“调用函数会占空间”的这么一个说法。但是如下示例时:
function f(x) return g(x)
end
lua解释器利用“调用g后不会再做任何事情,这种情况下当被调用函数g结束时程序不需要返回到调用者f”的特性,直接采用类似goto的做法跳转到g(x)执行,所以理论上尾调用递归的层次可以无限制的。但是需要分清什么是尾调用,只有return g(...)的情况才是,任何其他情况比如先g(...)再return,或者return g(...)+1这种都需要开辟额外栈空间。