1. 程式人生 > >lua學習之閉包實現原理

lua學習之閉包實現原理

引入 內嵌 種類 同時 概念比較 就會 類型 種類型 賦值語句

感覺學習lua的過程中, 閉包的概念比較難以理解,這裏記錄下對閉包的學習。

閉包的概念

在Lua中,閉包(closure)是由一個函數和該函數會訪問到的非局部變量(或者是upvalue)組成的,其中非局部變量(non-local variable)是指不是在局部作用範圍內定義的一個變量,但同時又不是一個全局變量,主要應用在嵌套函數和匿名函數裏,因此若一個閉包沒有會訪問的非局部變量,那麽它就是通常說的函數。也就是說,在Lua中,函數是閉包一種特殊情況。在Lua中,函數是一種第一類型值(First-Class Value),它們具有特定的詞法域(Lexical Scoping)。

第一類型值表示函數與其他傳統類型的值(例如數字和字符串類型)具有相同的權利。即函數可以存儲在變量或table。

function foo(x) print(x) end

  實質是等價於

foo = function (x) print(x) end

  因此一個函數定義實質就是一條賦值語句,這條語句創建了一種類型為“函數”的值,並賦值給一個變量。可以將表達式function (x) <body> end 視為一種函數構造式,就像table的構造式{}一樣。

詞法域是指一個函數可以嵌套在另一個函數中,內部的函數可以訪問外部函數的變量的這樣一種特征。比如:

function f1(n)  
   --函數參數n也是局部變量  
   local function f2()  
      print(n)   --引用外部函數的局部變量  
   end  
   return f2  
end  
  
g1 = f1(2015)  
g1() -- 打印出2015  
  
g2 = f1(2016)  

  

  註意這裏的g1和g2的函數體相同(都是f1的內嵌函數f2的函數體),但打印值不同。這是因為創建這兩個閉包時,他們都擁有局部變量n的獨立實例。事實上,Lua編譯一個函數時,會為他生成一個原型(prototype),其中包含了函數體對應的虛擬機指令、函數用到的常量值(數,文本字符串等等)和一些調試信息。在運行時,每當Lua執行一個形如function...end 這樣的表達式時,他就會創建一個新的數據對象,其中包含了相應函數原型的引用及一個由所有upvalue引用組成的數組,而這個數據對象就稱為閉包。由此可見,函數是編譯期概念,是靜態的,而閉包是運行期概念,是動態的。g1和g2的值嚴格來說不是函數而是閉包,並且是兩個不相同的閉包,而每個閉包能保有自己的upvalue值,所以g1和g2打印出的結果當然就不相同了。

這裏的函數f2可以訪問參數n,而n是外部函數f1的局部變量。在f2中,變量n即不是全局變量也不是局部變量,將其稱為一個非局部變量(non-local variable)或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  

  註意上面的例子中,閉包f1和f2共享同一個upvalue了,這是因為當Lua發現兩個閉包的upvalue指向的是當前堆棧上的相同變量時,會聰明地只生成一個拷貝,然後讓這兩個閉包共享該拷貝,這樣任一個閉包對該upvalue進行修改都會被另一個探知。

閉包的實現原理

當Lua編譯一個函數時,它會生成一個原型(prototype),原型中包括函數的虛擬機指令、函數中的常量(數值和字符串等)和一些調試信息。在任何時候只要Lua執行一個function .. end表達時,它都會創建一個新的閉包(closure)。每個閉包都有一個相應函數原型的引用以及一個數組,數組中每個元素都是一個對upvalue的引用,可以通過該數組來訪問外部的局部變量(outer local variables)。值得註意的是,在Lua 5.2之前,閉包中還包括一個對環境(environment)的引用,環境實質就是一個table,函數可以在該表中索引全局變量,從Lua 5.2開始,取消了閉包中的環境,而引入一個變量_ENV來設置閉包環境。由此可見,函數是編譯期概念,是靜態的,而閉包是運行期概念,是動態的。

lua學習之閉包實現原理