lua閉包全面解析

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

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

function f1(n)
   --函數參數n也是局部變量
   local function f2()
      print(n)   --引用外部函數的局部變量
   end
   return f2
end

g1 = f1(2015)
g1() -- 打印出2015

g2 = f1(2016)
g2() -- 打印出2016

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

function Create(n)
   local function foo1()
      print(n)
   end
   local function foo2()
      n = n + 10
   end
   return foo1,foo2
end

f1,f2 = Create(2015)
f1() -- 打印2015

f2()
f1() -- 打印2025

f2()
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不再被任何閉包所引用,那麼它的存儲空間就會被回收。

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

function values(t)
	local i = 0
	return function () i = i + 1 return t[i] end
end

t = {10, 20, 30}

iter = values(t)
while true do
	local element = iter()
	if element == nil then break end
	print(element)
end

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


發佈了57 篇原創文章 · 獲贊 108 · 訪問量 39萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章