Lua程序设计第二版(笔记) 第六章深入函数

Lua中,函数是一种“第一类值”,它们具有特定的词法域。

“第一类值”表示在Lua中函数与其他传统类型的值具有相同的权利。函数可以存储到变量中(无论全局变量还是局部变量)或table中,可以作为实参传递给其他函数,还可以作为其他函数的返回值。

“词法域”是指,一个函数可以嵌套在另一个函数中,内部的函数可以访问外部函数中的变量。

Lua中一个容易混淆的概念是,函数与所有其他值一样都是匿名的,即它们都没有名称。当讨论一个函数名时,实际上是在讨论一个持有某函数的变量。与其他变量持有各种值是一个道理,可以用多种方式操作这些变量。

例如:

a = { p = print }

a.p("hello world") --hello world

print = math.sin --print现在引用了正弦函数

a.p( print(1) )--0.841470

sin = a.p --sin现在引用了print函数

sin( 10 ,20 ) --10 20

函数就是由一些表达式创建的。

例如:

常见的函数编写方式:

function foo ( x ) return 2*x end 

可以将上面函数编写的代码换成一种简化书写形式:

foo = function( x ) return 2*x end  

函数在Lua中是一种"第一类值",所以不仅可以将其存储在全局变量中,还可以存储在局部变量中甚至table的字段值中。 


closure(闭合函数)

词法域:

names = { "Peter", "Paul", "Mary" }
grades = { Mary = 10, Paul = 7, Peter = 8 }
table.sort( names, function( n1, n2)
return grades[n1] > grades[n2]
end
)

现在创建一个函数来做:

function sortbygrade( names, grades )

table.sort( names, function( n1, n2 ) 

return grades[n1] > grades[n2]  -- grades非局部变量

end)

end

传递给sort的匿名函数可以访问参数grades,而grades是外部函数sortbygrade的局部变量。在这个匿名函数内部,grades既不是全局变量也不是局部变量,将其称为一个"非局部的变量"。只所以存在这种访问,因为函数在Lua中是"第一类值"。

function newCounter()
local i = 0
return function() -- 匿名函数
i = i + 1
return i
end
end

c1 = newCounter()
print( c1() ) -- 1
print( c1() ) -- 2

匿名函数访问了一个"非局部的变量i",该变量用于保持一个计数器。

一个closure就是一个函数加上该函数所需访问的所有"非局部的变量"。如果再次调用newCounter,那么它会创建一个新的局部变量i,从而也将得到一个closure

c2 = newCounter()

print( c2() ) -- 1

print( c1() ) -- 3

print( c2() ) -- 2

c1和c2 是同一个函数所创建的两个不同的closeure,它们各自拥有局部变量i的独立实例。

Lua中函数是存储在普通变量中的,因此可以轻易地重新定义某些函数,甚至是重新定义那些预定义的函数。通常重新定义一个函数时,需要在新的实现中调用原来的那个函数。例如:重新定义函数sin,使其参数能使用角度来代替原来的弧度。那么这个新函数就必须得转换它的实参,并调用原来的sin函数来完成真正的计算。

oldSin = math.sin

math.sin = function(x)

return oldSin( x*math.pi/180 )

end

另有一种更彻底的做法:

do

local oldSin = math.sin

local k = math.pi/180

math.sin = function(x)

return oldSin(x*k)

end

end


非全局的函数

在Lua中创建函数,只需要将常规的函数语法与table语法结合起来使用即可

例如:

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

}

Lua中还提供了另一种语法来定义

Lib = {}

function Lib.foo ( x, y ) return x + y  end

function Lib.goo( x,y ) return x - y end

将一个函数存储到一个局部变量中,即得到一个"局部函数",也就是说该函数只能在某个特定的作用域中使用。词法域确保了程序包中的其他函数可以使用这些局部函数:

local f = function(<参数>)

<函数体>

end

local g = function(<参数>)

<一些代码>

f()  -- f在这里是可见的

<一些代码>

end

对于这种局部函数的定义,Lua还支持一种特殊"语法糖":

local function f(<参数>)

<函数体>

end

在定义递归的局部函数时,还有一个特别之处需要注意。例如下面代码:

local fact = function (n)

if n == 0 then return 1

else return n * fact(n-1) -- 错误

end

end

当Lua编译到函数体中调用fact(n-1)的地方时,由于局部的fact尚未定义完毕,因此这句表达式其实调用了一个全局的fact,而非此函数自身。为了解决此问题,可以先定义一个局部变量然后再定义函数本身:

local fact

fact = function ( 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中所引用的那个)置于未定义的状态。


正确的尾调用

所谓尾调用就是一种类似于goto的函数调用。当一个函数调用是另一个函数的最后一个动作时,该调用才算是一条“尾调用”。

例如:

function f (x) return g(x) end

当f调用完g之后就再无其他事情可做了。当g返回时,执行控制权可以直接返回到调用f的那个点上。使得在进行"尾调用"时不耗费任何栈空间。这种实现称为“尾调用消除”。

判断是否为"尾调用"的准则是:一个函数在调用完另一个函数之后,是否就无其他事情需要做了。

function f (x) g (x) end

这个示例问题在于,当调用完g后,f并不能立即返回,它需要丢弃g返回的临时结果。

不符合尾调用的准则的例子

return g(x) + 1 -- 必须做一次加法

return x or g(x) --必须调整为一个返回值

return ( g( x ) ) --必须调整为一个返回值

在Lua中,只有"return <func>(args)"这样的调用形式才算是一条"尾调用"。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值