lua閉包全面解析

Lua中,閉包(closure)是由一個函數和該函數會訪問到的非局部變量(或者是upvalue)組成的,其中非局部變量(non-local variable)是指不是在局部作用範圍內定義的一個變量,但同時又不是一個全局變量,主要應用在嵌套函數和匿名函數裏,因此若一個閉包沒有會訪問的非局部變量,那麼它就是通常說的函數。也就是說,在Lua中,函數是閉包一種特殊情況簡而言之,閉包就是一個函數加一個upvalue。那麼接下來看下upvalue是啥。

Lua使用結構體upvalue來實現閉包。外面的局部變量可以直接通過upvalue進行訪問。upvalue最開始的時候指向棧中的一個變量,此時這個變量還在它的生存週期內。當變量離開作用域(譯者注:就是函數返回後,變量的生存週期結束時),這個變量就從棧轉移到了upvalue中。雖然這個變量存儲在upvalue中,但是訪問這個變量還是間接通過upvalue中的一個指針進行的(譯者注:和在棧中時候的訪問方式一樣)。因此,變量位置的轉移對任何試圖讀寫這個變量的代碼都是透明的。有別於這個變量在一個函數內部時候的行爲,函數聲明、訪問這個變量,就是直接對棧的操作。看下具體例子:

[cpp] view plain copy
  1. function f1(n)  
  2.    --函數參數n也是局部變量  
  3.    local function f2()  
  4.       print(n)   --引用外部函數的局部變量  
  5.    end  
  6.    return f2  
  7. end  
  8.   
  9. g1 = f1(2015)  
  10. g1() -- 打印出2015  
  11.   
  12. g2 = f1(2016)  
  13. g2() -- 打印出2016  

這裏的n就是upvalue。upvalue實際指的是變量而不是值,這些變量可以在內部函數之間共享,即upvalue提供一種閉包之間共享數據的方法,再看個例子:

[cpp] view plain copy
  1. function Create(n)  
  2.    local function foo1()  
  3.       print(n)  
  4.    end  
  5.    local function foo2()  
  6.       n = n + 10  
  7.    end  
  8.    return foo1,foo2  
  9. end  
  10.   
  11. f1,f2 = Create(2015)  
  12. f1() -- 打印2015  
  13.   
  14. f2()  
  15. f1() -- 打印2025  
  16.   
  17. f2()  
  18. f1() -- 打印2035  

上面的例子中,閉包f1f2共享同一個upvalue了,這是因爲當Lua發現兩個閉包的upvalue指向的是當前堆棧上的相同變量時,會聰明地只生成一個拷貝,然後讓這兩個閉包共享該拷貝,這樣任一個閉包對該upvalue進行修改都會被另一個探知。爲什麼會這樣,我們看下面的解釋:

通過爲每個變量最多創建一個upvalue並按需要重複利用這個upvalue保證了未決狀態(未超過生命週期)的局部變量(pending vars)能夠在閉包之間正確地共享。爲了保證這種唯一性,Lua維護這一條鏈表,該鏈表中每個節點對應一個打開的upvalueopend upvalue)結構,打開的upvalue是指當前正指向棧局部變量的upvalue,如上圖的未決狀態的局部變量鏈表(the pending vars list)。當Lua創建一個新的閉包時,Lua會遍歷當前函數所有的外部的局部變量,對於每一個外部的局部變量,若在上面的鏈表中能找到該變量,則重複使用該打開的upvalue,否則,Lua會創建一個新的打開的upvalue,並把它插入鏈表中。當局部變量離開作用域時(即超過變量生命週期),這個打開的upvalue就會變成關閉的upvalueclosed upvalue),並把它從鏈表中刪除,一旦某個關閉的upvalue不再被任何閉包所引用,那麼它的存儲空間就會被回收。

最後看下閉包的應用。閉包最常用的一個應用就是實現迭代器。所謂迭代器就是一種可以遍歷一種集合中所謂元素的機制。每個迭代器都需要在每次成功調用之間保持一些狀態,這樣才能知道它所在的位置及如何進到下一個位置。閉包剛好適合這種場景。比如下面的代碼:

[cpp] view plain copy
  1. function values(t)  
  2.     local i = 0  
  3.     return function () i = i + 1 return t[i] end  
  4. end  
  5.   
  6. t = {10, 20, 30}  
  7.   
  8. iter = values(t)  
  9. while true do  
  10.     local element = iter()  
  11.     if element == nil then break end  
  12.     print(element)  
  13. end  

總結下lua閉包,關鍵點是upvalue,然後注意下如何申明一個揹包,函數(A)裏面返回的是函數(B),B引用了A的局部變量。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章