第六课 深入函数

在Lua中,函数是一种“第一类值”(和数字、字符串等一样,具有相同的权利),它们具有特定的词法域(一个函数可以嵌套在另一个函数中,内部函数可以访问外部函数中的变量)。函数式语言,函数式编程。
在Lua中有一个容易混淆的概念是,函数与其他所有值一样 都是匿名的,即它们都没有名称。当讨论一个函数名时(例如print),实际上是在讨论一个持有某函数的变量。这与其他变量持有各种值是一样的道理。可以以多种方式来操作这些变量。
a = {p = print}
a.p("Hello World")
print =math.sin
a.p(print(1))
sin = a.p
sin(10, 20)

Lua中最常见的就是 函数编写方式,如:
function foo(x) return 2 * x end
只是一种“语法糖”而已,上面这句只是一下代码 的一种简化书写形式:
foo = function (x) return 2 * x end
因此,一个函数定义实际上就是一条语句(更准确地说就是一条赋值语句),这条语句创建了一种类型为“函数”的值。并将这个值赋予一个变量。可以将表达式“function(x) <body> end”视为一种函数的构造式,就像table的构造式{}一样。将这种函数构造式的结果称为一个“匿名函数”。虽然一般情况下,会将函数赋予全局变量,即给予其一个名称。但在某些特殊情况中,仍会需要用到匿名函数。

table库中的函数table.sort,对table中的元素进行排序。sort函数没有提供排序准则(按升序或降序、按数字顺序等),而是提供了一个所谓”次序函数“。这个函数接受两个元素,并返回在有序情况下第一个元素是否应该排在第二个元素前面。
network = {
{name = "grauna", IP = "210.26.30.20"}
{name = "arraial", IP = "210.26.30.21"}
{name = "lua", IP = "210.26.30.22"}
{name = "derain", IP = "210.26.30.23"}
}
如果以name字段、按照反向的字符顺序来对这个table排序的话,只需要这么写:
table.sort(network, function (a, b) return (a.name > b.name) end)
像sort这样的函数,接受另一个函数作为其实参的,称其为一个“高阶函数”。

闭合函数closure
若将一个函数写在另一个函数之内,那么位于内部的函数便可以访问外部函数中的局部变量,这项特征称之为“词法域”。
function sortbygrade(names, grades)
table.sort(names, function(n1, n2) return grades[n1] > grades[n2] end)
end
内部的匿名函数可以访问外部函数sortbygrade的参数grades,即sortbygrade的局部变量。在这个匿名函数内部,grades既不是全局变量,也不是局部变量,将其称为一个“非局部的变量”。
从技术上讲,Lua中只有closure,而不存在“函数”,因为函数本身就是一种特殊的closure。
可以使用这样的技术来创建一个安全的运行环境,即所谓的“沙盒( sandbox)”
do
local oldOpen = io.open
local access_ok = function(filename ,mode)
<检查访问权限>
end
io.open = function(filename, mode)
if access_ok(filename, mode) then
return oldOpen(filename, mode)
else
return nil, "access denied"
end
end
end
经过重新定义后,一个程序就只能通过新的受限版本来调用原来的那个未受限制的open函数了。

非全局的函数
函数不仅可以存储在全局变量中,还可以存储在table的字段中和局部变量中。
将函数存储在table字段中,大部分Lua库都使用了这种机制。(io.read,math.sin)
Lib = {}
Lib.foo = function (x, y) return x + y end
Lib.goo = function (x, y) return x - y end
或者
Lib = {
foo = function (x, y) return x + y end
goo = function (x, y) return x - y end
}
或者
Lib = {}
function Lib.foo(x, y) return x + y end
function Lib.goo(x, y) return x - y end
局部函数
local f = function (参数)
<函数体>
end
或者(局部函数定义语法糖)
local function f(参数)
<函数体>
end
在定义递归的局部函数时,还有一个特别之处需要注意。
local fact = function (n)
if n == 0 then return 1
else return n * fact(n -1) --错误,这时局部的fact还未定义完,这里
的fact只能是表示全局函数fact
end
end
修改为:
local fact
fact = function (n)
if n == 0 then return 1
else return n * fact(n -1)
end
end
当Lua展开局部函数定义的语法糖时,并不是使用基本函数定义语法。而是对于局部函数定义:
local function foo (<参数>) <函数体> end
Lua将其展开为:
local foo
foo = function (<参数>) <函数体> end
因此,使用这种语法来定义递归函数不会产生错误:
local function fact(n)
if n == 0 then return 1
else return n * fact(n - 1)
end
end
这个技巧对于间接递归的函数而言是无效的。在间接递归的情况中,必须使用一个明确的前向声明。
local f, g
function g()
f()
end
function f()
g()
end
别把第二个函数定义为“local function f”如果那样的话,Lua会创建一个全新的局部变量f,而将原来声明的f(函数g中所引用的那个)置于未定义的状态。

正确的尾调用
Lua支持“尾调用消除”(类似goto的调用)。当一个函数调用是另一个函数的最后一个动作时,该调用才算是一条“尾调用”。
function f(x) return g(x) end --g(x)的调用就是一条尾调用
f调用完g之后就再无其他事情可做了。因此,程序就不需要返回那个“尾调用” 所在的函数了。在“尾调用”之后,程序也不需要保存任何关于该函数的栈信息了。有一些语言实现可以得益于这个特点,使得在进行“尾调用”时不耗费任何栈空间。将这种实现称之为支持“尾调用消除”。
“尾调用”不会耗费栈空间,所以一个程序可以拥有无数嵌套的“尾调用”。
在调用一下函数时,传入任何数字作为参数都不会引起栈溢出。
function foo (n)
if n > 0 then return foo(n - 1) end
end
在Lua中,只有“return <func>(<args>)”这样的调用形式才算是一条“尾调用”。Lua在调用前对<func>以及其参数求值,所以它们可以是任何复杂的表达式。如:
return x[i].foo(x[j] + a * b, i + j)
尾调用类似goto,因此在Lua中“尾调用”的一大应用就是编写“状态机”。这种程序通常以一个函数来表示一个状态,改变状态就是goto到另一个特定的函数。










  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值