Lua的upvalue和闭包
本站文章均為Jensen抹茶喵原創,轉載務必在明顯處注明:
轉載自【博客園】 原文鏈接:http://www.cnblogs.com/JensenCat/p/5112420.html
1.什么是閉包
支持閉包特性通常需要一個嵌套函數,通過執行嵌套函數來改變所在父函數的局部變量狀態,父函數保存調用上下文狀態,而嵌套函數負責修改狀態的改變.(簡單來說就是得支持函數嵌套)
下面就是一個Lua閉包:
?
2.閉包的作用
下面是一個java的類,getName方法獲取到了類對象的私有成員變量
class Person {private String name;public String getName(){return name; } }?
?通過上面的方式可以獲取到一個類內部的私有屬性,同樣的,在lua中可以通過某個方法來獲取這個方法的局部變量,然后通過這個方法內的方法來讀取想要的變量值。
function func3()local num3 = 44function func4()return num3endreturn func4 endlocal func = func3(); print(func())?
?
解釋:
1.在外部無法獲取到func3內部的局部變量,但是func3內部的局部方法func4卻可以獲取到,因此返回一個func4的引用?,這樣在外部通過這個func4就可以獲取到func3的內部變量。
2.雖然是繞了一個圈子,但是在方法外部卻通過這樣一個手段獲取到了內部的值。而這個方法內的局部方法func4就叫做閉包,按照很多書上的概念,這個方法搭建了方法內部與方法外部的橋梁,使得在外部也可以任意的獲取到方法內部的資源。
3.但是閉包會造成變量在內存中持久占用,因此會有一定的性能問題,最好不要輕易使用,即便使用也要在恰當的實際進行釋放。
?
3.游戲開發中的應用
?
--以下用cocos2dx中的Lua來舉例... --2dx通過tolua++把類方法導出--舉例api --按鈕響應回調函數格式為: --luaFunc(event) --event為觸摸按下,觸摸移動,觸摸離開等事件--lua中的API為: --UIButton::addListenHandler(luaFunc)--實際需求是我按鈕按下時,我需要改變按鈕自身的紋理...此時回調中卻沒有按鈕本身的對象(sender),怎么辦呢?--利用閉包就輕松解決了--下面是LUA實戰例子:一個testUI的頁面類 local testUI = testUI or {}local testUI:onBtnClick(sender,event)--可獲取的參數有:隱藏的self,btn,event endfunction testUI:initButton()local btn = UIButton:create()--重點來了btn:addListenHandler(function(event)--使用閉包把self,btn都傳進去了....self:onBtnClick(btn,event)end) endreturn testUI4.lua函數遞歸以及尾調用消除
1)遞歸示例:
--反面遞歸例子(遞歸必須在初始化以后才能調用) local func = function(n)if n > 0 then return func(n - 1) --此處調用錯誤 end--正確例子1 local func func = function(n)if n > 0 then return func(n - 1) --此處調用錯誤 end--正確例子2(此處函數展開后解釋為例子1的代碼再執行) function func(n)if n > 0 then return func(n - 1) --此處調用錯誤 end--如果是兩個函數嵌套遞歸(超前遞歸,必須先聲明) local g local f--這里不能加local..不然等于聲明了多一個局部變量了,遞歸的對象就不對了 function g()f() end--這里不能加local..不然等于聲明了多一個局部變量了,遞歸的對象就不對了 function f()g() end?
2)尾調用消除(遞歸的時候如果返回的函數是最后的執行...則不損耗棧空間,相當于GOTO語句)
?
--尾調用消除 function g()return a,b end--正確例子 function f()return g() --正確的尾調用消除 end--錯誤例子1 function f(x)return g() + 1 --最后執行的是加法 end--錯誤例子2 function f(x)return (g()) --最后執行的是強制返回1個值 end--錯誤例子3 function f(x)return x or g() end ?總結:由于LUA尾遞歸調用這個性質,我們可以用GOTO來實現狀態機了=====================================================================
?
Lua函數可以被當成參數傳遞,也可以被當成結果返回,在函數體中仍然可以定義內嵌函數。lua閉包是Lua函數生成的數據對象。每個閉包可以有一個upvalue值,或者多個閉包共享一個upvalue數值。
1、upvalue
如果函數f2定義在函數f1中,那么f2為f1的內嵌函數,f1為f2的外包函數,外包和內嵌都具有傳遞性,即f2的內嵌必然是f1的內嵌,而f1的外包也一定是f2的外包。
內嵌函數可以訪問外包函數已經創建的局部變量,而這些局部變量則稱為該內嵌函數的外部局部變量(或者upvalue)
代碼如下:
function f1(n)-- 函數參數也是局部變量local function f2()print(n) -- 引用外包函數的局部變量endreturn f2 end g1 = f1(1979) g1() -- 打印出1979 g2 = f1(500) g2() -- 打印出500當執行完g1 = f1(1979)后,局部變量n的生命本該結束,但因為它已經成了內嵌函數f2的upvalue,它又被賦給了變量g1,所以它仍然能以某種形式繼續“存活”下來,從而令g1()打印出正確的值。
2、閉包
Lua編譯一個函數時,其中包含了函數體對應的虛擬機指令、函數用到的常量值(數,文本字符串等等)和一些調試信息。在運行時,每當Lua執行一個形如function...end 這樣的函數時,它就會創建一個新的數據對象,其中包含了相應函數原型的引用、環境(用來查找全局變量的表)的引用以及一個由所有upvalue引用組成的數組,而這個數據對象就稱為閉包。由此可見,函數是編譯期概念,是靜態的,而閉包是運行期概念,是動態的。g1和g2的值嚴格來說不是函數而是閉包,并且是兩個不相同的閉包,而這兩個閉包保有各自的upvalue值。
使用upvalue代碼如下:
g1()打印出來的是1989,原因是打印的是upvalue的值。
upvalue實際是局部變量,而局部變量是保存在函數堆棧框架上的,所以只要upvalue還沒有離開自己的作用域,它就一直生存在函數堆棧上。這種情況下,閉包將通過指向堆棧上的upvalue的引用來訪問它們,一旦upvalue即將離開自己的作用域,在從堆棧上消除之前,閉包就會為它分配空間并保存當前的值,以后便可通過指向新分配空間的引用來訪問該upvalue。當執行到f1(1979)的n = n + 10時,閉包已經創建了,但是變量n并沒有離開作用域,所以閉包仍然引用堆棧上的n,當return f2完成時,n即將結束生命,此時閉包便將變量n(已經是1989了)復制到自己管理的空間中以便將來訪問。?
3、upvalue和閉包數據共享
upvalue還可以為閉包之間提供一種數據共享的機制。
(1)單重內嵌函數的閉包 (函數創建的閉包)
一個函數創建的閉包共享一份upvalue。
代碼如下:
function Create(n)local function foo1()print(n)endlocal function foo2()n = n + 10endreturn foo1,foo2 endf1,f2 = Create(1979)--創建閉包 f1() -- 打印1979 f2() f1() -- 打印1989 f2() f1() -- 打印1999f1,f2這兩個閉包的原型分別是Create中的內嵌函數foo1和foo2,而foo1和foo2引用的upvalue是同一個,即Create的局部變量n。執行完Create調用后,閉包會把堆棧上n的值復制出來,那么是否f1和f2就分別擁有一個n的拷貝呢?其實不然,當Lua發現兩個閉包的upvalue指向的是當前堆棧上的相同變量時,會聰明地只生成一個拷貝,然后讓這兩個閉包共享該拷貝,這樣任一個閉包對該upvalue進行修改都會被另一個探知。上述例子很清楚地說明了這點:每次調用f2都將upvalue的值增加了10,隨后f1將更新后的值打印出來。upvalue的這種語義很有價值,它使得閉包之間可以不依賴全局變量進行通訊,從而使代碼的可靠性大大提高。
(2)多重內嵌函數的閉包 (閉包創建的閉包)
同一閉包創建的其他的閉包共享一份upvalue。
閉包在創建之時其需要的變量就已經不在堆棧上,而是引用更外層外包函數的局部變量(實際上是upvalue)。
function Test(n)local function foo()local function inner1()print(n)endlocal function inner2()n = n + 10endreturn inner1,inner2endreturn foo endt = Test(1979)--創建閉包(共享一份upvalue) f1,f2 = t()--創建閉包 f1() -- 打印1979 f2() f1() -- 打印1989 g1,g2 = t() g1() -- 打印1989 g2() g1() -- 打印1999 f1() -- 打印1999執行完t = Test(1979)后,Test的局部變量n就結束生命周期了,所以當f1,f2這兩個閉包被創建時堆棧上根本找不到變量n。Test函數的局部變量n不僅是foo的upvalue,也是inner1和inner2的upvalue。t = Test(1979)之后,閉包t ?已經把n保存為upvalue,之后f1、f2如果在當前堆棧上找不到變量n就會自動到它們的外包閉包(這里是t的)的upvalue引用數組中去找.
??g1和g2與f1和f2共享同一個upvalue。因為g1和g2與f1和f2都是同一個閉包t 創建的,所以它們引用的upvalue ?(變量n)實際也是同一個變量,而它們的upvalue引用都會指向同一個地方。
總結
以上是生活随笔為你收集整理的Lua的upvalue和闭包的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Lua 模块与包
- 下一篇: Lua 语言中的点、冒号与self