入门客AI创业平台(我带你入门,你带我飞行)
博文笔记

lua--函数深入:闭合函数,局部函数,尾调用

创建时间:2015-06-13 投稿人: 浏览次数:3086

lua函数具有两大特征:函数作为第一类值,函数具有特定的词法域(Lexical Scoping)

所谓第一类值:代表函数和其他传统类型的值是等价的(例如数字和字符串),函数可以同他们一样存储在变量,table中,可以作为实参传递,可以作为函数返回值。

对于第一类值,需要讲明,函数和其他值一样都是匿名的,是没有名字的。而我们平时所说的函数名,如print(),都只是一种语法糖,一个持有某个函数的变量。

例如:

function foo(x) return 2*x end                           --->等价于                                  foo = function(x)   return 2*x end     

函数定义实际上是一条赋值语句。这条语句先创建一个类型为函数的值,然后将其赋值给一个变量。

我们将表达式“function (x) <body> end”视为函数的构造式,又被称其为匿名函数,在某些特殊情况下,我们会用到匿名函数。


特定的词法域(Lexical Scoping):指一个函数可以嵌套在另一个函数中,内部的函数可以访问外部函数的变量。



闭合函数(closure)

由于词法域的原因,当一个函数写在另一个函数内部,而这个内部函数可以访问外部函数的局部变量,这个被访问的局部变量既不是全局的也不是局部的,被称为非局部的变量(non-local variable).

所以闭合函数就是这个内部函数加上该函数需要访问的所有的非局部变量的函数。

关于闭包的实现原理,推荐以下博客http://blog.csdn.net/maximuszhou/article/details/44280109。

关于闭包,有几个需要注意的地方:

e.g.

function newCounter()
	local i = 0;
	return function()
		i = i+1
		return i
	end
end

c = newCounter()   -- newCounter返回匿名函数赋予变量c,此时c是一个闭包,同时拥有一个upvalue值
print(c())   -->1
print(c())   -->2      --匿名函数再次访问闭包中的upvalue值,使其加1.

--注意,如果再次调用newCounter,它将创建一个新的闭包,该闭包将拥有一个新的upvalue.
c1 = newCounter()    -- 返回新的闭包,赋值给变量c1.

--同时需要注意:如果一个函数内部,有多个闭合函数,这些闭合函数如果需要访问同一个外部变量,那么这个upvalue在这些闭包之间是共享的。、
--e.g.
function f()
	local i = 0
	local function foo1()
		i = i +1
		return print(i)
	end
	local function foo2()
		i = i+10
		return print(i)
	end
	return foo1, foo2
end
t1,t2 = f()             --   t1,t2闭包函数共享i这个upvalue值。
t1()                    -->1
t2()                    -->11
闭包有很多作用,主要体现在,当上一级函数的局部变量被释放后,还可以通过闭包函数的upvalue值获的访问。



非局部的函数(non-local function) 函数不仅可以存储在全局变量中,还可以存储在table字段和局部变量中。因为函数是第一类值
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} 和这种方式: lib = {} function lib.foo(x,y) return x+y end function lib.goo(x,y) return x-y end

将函数存储在局部变量中,便得到一个局部函数。局部函数只能在特定的作用域中使用。 lua将每个程序块(chunk)作为一个函数来处理,所以里面声明的函数就是局部函数,只在该程序块中可见。词法域保证了程序包中的其他函数可以使用这些局部函数 比如: local f = function () <函数体> end local g = function() <代码> f()                    --   f()在这里可见 <代码> end
对于局部函数的定义,lua也提供一种语法糖: loacal function f() <函数体> end
注意,当Lua在展开这种语法糖时,并不使用基本函数定义语法。而是;将其展开为以下这种: local f f = function() <函数体> end      --对比基本函数定义语法的不同:local f = function() <函数体> end
lua这样做,是有用处的。 比如在定义递归的局部函数时,如果采用了基本函数定义语法,大多是错误的: 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() if n==0 then return 1 else return n*fact(n-1) end end 现在,fact()的调用就表示了局部变量,即使在定义时,这个局部变量的值还没有完成定义,但在执行时,fact已经拥有了正确的值。
当然解决方法,也可以用上面提到的语法糖,因为lua对其的展开并不是使用基本函数定义语法。 so: local function fact(n) if n == 0 then return 1 else return n* fact(n-1) end end 这种方式完全等价于上面。

这个技巧对于间接递归的函数是无效的,在这种情况中,必须使用明确的前向声明(forward declaration) local f, g        --明确的前向声明 function g() <code>f()<code> end
function f() <code>g()<code> end 注意,别把第二个函数定义写为“local function f”.那么lua会创建一个新的local f,而原来声明的f将置于未定义状态。


正确的尾调用(proper tail call)
lua还支持尾调用消除(tail-call elimination)这一特性。 尾调用:当一个函数是另一个函数的最后一个动作时,该调用就是一条尾调用。 尾调用消除:当在进行尾调用时不消耗任何栈空间,这种实现就称为尾调用消除。 e.g. function f(x) return g(x) end 当程序执行完g(x)之后,程序就不需要回到f()这个函数了,因为已经无事情可做了,这时程序也不需要保存任何关于f()函数的栈信息了。当g返回,程序的控制权直接返回到调用f的那个点上。 简单来说,就是程序在进入g(x)后,上一级函数的栈空间被完全释放,从而节省了内存。
正是这个尾调用消除特性,使得一个程序可以拥有无数嵌套的尾调用,而不会造成栈溢出。 function foo(n) if n>0 then return foo(n-1) end end
当然要享受尾调用消除这个特性,就必须保证这个调用时尾调用。判断的标准就是,在调用玩这个函数之后,上一级的函数就无事情可做了。 比如以下一些错我例子: funtion f(x) g(x) end            -- 调用完g后,还需要返回f,丢弃g返回的临时结果。 return  g(x) + 1        -- 还要做一次加法 return x or g(x)        -- 还要将其调整为一个返回值 return (g(x))        -- 还要将其调整为一个返回值
只有这种形式的调用才是正确的:return <func>(<args>)   .

由于一条尾调用就好比一条goto语句,所以尾调用的一大应用就是编写状态机(state machine)   
























声明:该文观点仅代表作者本人,入门客AI创业平台信息发布平台仅提供信息存储空间服务,如有疑问请联系rumenke@qq.com。
  • 上一篇:没有了
  • 下一篇:没有了
未上传头像