如果損害到原作者的利益,請聯繫,立即刪除!!
本文通過兩個函數徹底搞懂Lua中的閉包,相信看完這兩個函數,應該能理解什麼是Lua閉包。廢話不多說,上 code:
--[[***************************************************************************
I'm jacc.kim
CreateDate: 2017-08-18 11:19:20
FileName : closure.lua
Version : 1.00
Author : jacc.kim
Summary :
***************************************************************************--]]
--[[
* summary : 利用閉包實現帶狀態的迭代器
* param : collection 集合.
* return : !!!重點注意:函數的返回值是重點.返回值是:一!個!函!數!.而該返回的函數是一個閉包.
--]]
local function elementIterator(collection)
-- nIndex 爲 elementIterator() 函數的一個局部變量.標記着 collection 當前處理到的 collection 的下標位置 .
-- 這邊爲每個閉包"實例"初始化它們的"成員變量"的值.(不理解這句的,可先跳過)
local nIndex = 0;
-- nCollectionCount 爲 elementIterator() 函數的一個局部變量.記錄着命令 collection 的長度信息
-- 這邊爲每個閉包"實例"初始化它們的"成員變量"的值.(不理解這句的,可先跳過)
local nCollectionCount = #collection;
-- 閉包.
-- 說明:01.在 Lua 中,函數內部可以定義函數,被定義的內部函數可以訪問到外層函數定義的局部變量(如:上面的 nIndex、nCollectionCount、collection等,注意:
-- 外層函數的參數其實就是個局部變量).
-- 02.並且非常重要的(閉包的特性:)這些外層函數定義的局部變量的狀態會被閉鈕函數保持住.如 nIndex 起初的時候爲 0 值,執行完下面的
-- nIndex = nIndex + 1; 後, nIndex 的值爲 1,之後,再次調用該閉包函數時, nIndex = nIndex + 1; 後, nIndex 的值就變爲 2.
-- 03.在Lua中,函數是第一類值.因此,函數可以被賦值給一個變量(局部或全局)、可以作爲函數參數傳遞、可以作爲函數的返回值返回.因此,這邊可以 return 該閉包函數.
return function ()
-- nIndex 爲 外層函數的一個局部變量.但 nIndex 的狀態會被該內部函數(可理解爲閉包函數)記憶保持住.因此,每次調用該閉包函數量.nIndex的值部是不斷累加的.
-- (有點類似 C++ 的 static 特性(注意:僅僅只是類似,不能等同).如:
-- static auto nIndex = 0;
-- nIndex = nIndex + 1; // 或 ++nIndex
-- )
nIndex = nIndex + 1; -- feature of Colsure
-- nCollectionCount 爲外層函數的一個局部變量.同樣的該變量的狀態也會被保持住.但在該閉包函數中,沒有去改變 nCollectionCount 的值.因此,該值就一
-- 值爲集合 collection 的長度.
--[[
-- 現在重點分析一下這個閉包函數.在此之前,先再介紹另外一個Lua的特性.在Lua中,一個函數可以沒有返回值、可以只返回一個值、也可以返回多個值.重點就是要看上下文本
-- 到底需要一個函數返回多少個值出去.Ok,這點說明暫時先到此爲止.下面先分析一下該閉包函數.當調用該閉包函數時,
(!!!這點很重要啊)如果下面的 if 語句條件成立,則該函數會有一個返回值,返回外層函數的參數(collection)的對應索引位置處的元素.
(!!!這點更加重要)如果下面的 if 語句條件不成立,則該閉包函數是沒有返回值,或者根據上下文,可以返回多個的 nil 值.
--]]
if (nIndex <= nCollectionCount) then
return collection[nIndex];
end
end
end
--[[
* summary : 測試上面設計的利用閉包實現的迭代器
* param : collection 待測試的集合.
* !!!note : 01.此處的目的:通過該函數,我們徹底理解閉包在此處起到的作用.
--]]
local function traversalCollection()
-- collection 爲測試數據.
local collection = { "apple", "pear", "orange" };
-- 這是 Lua 的一個泛型 for 循環.完了完了,沒辦法理解該for循環?不怕,下面我們來徹底搞懂它(注意:搞懂該for循環,我們也就徹底理解什麼是閉包).請看下面的註釋.
--[[
for 循環的定義,其實可以理解爲:
for (條件)
do
// some code here........
end
如果for的條件成立,則會執行for後面的語句塊 do ... end 中的語句.否則for循環結束.
此處將下面for循環改寫成另外一種"等價"的寫法
local iter = elementIterator(collection);
for element = iter() do
print(element);
end
或者乾脆改寫成 while 語句更容易理解
local iter = elementIterator(collection); -- elementIterator()函數其實就好比一個工廠一樣,調用一次,產生一個閉包"實例"
local element = iter(); -- 注意:此處 iter() 就是在調用閉兇函數
while element do
print(element);
element = iter(); -- 注意:此處 iter() 就是在調用閉兇函數
end
前文已在 elementIterator() 中的閉包函數中詳細說明了閉包函數的特性,閉包函數會保持外層函數的狀態信息.因此,調用 iter() 函數將返回一個 collection 集合的
元素,再調用一次將返回下一個元素,一直調用,最後因元素遍歷結束,所以最終會返回一個nil被 element 接收.此時 while 循環結束(注意:Lua中 nil 與 false 都認爲是假).
至此,相當閉包大家也都理解了.
--]]
for element in elementIterator(collection) do
print(element);
end
end
traversalCollection();
--[[
上文比較神奇的地方有兩處:
一爲 local iter = elementIterator(collection); 中的 elementIterator(collection)
解釋:elementIterator(collection) 有點類似一個工廠.調用一次則會 產生一個閉包"實例"(該實例"地址"被保存到 iter 變量).而該閉包"實例"擁有
nIndex、nCollectionCount、collection "成員"變量.這些變量是它的自己的狀態屬性.
如果此時再調用一次 elementIterator(collection) 則又會產生一個新的閉包"實例".每個閉包實例都將有自己的一份狀態屬性.這些狀態屬性的初始化,都是
在閉包函數所在的外層函數處進行初始化(具體可見前文註釋).
二爲 element = iter(); 中的 iter()
解釋:iter() 簡單一點理解,其實就是在調用一個函數而已,僅此而已.然後這個函數返回一個值.只是這個函數比較特別,它有點像c++中的類一樣,有自己的狀態屬性,調用
時,它會根據自己所記錄的狀態屬性數據,進行相應邏輯處理,並返回對應的值.
總結:
Lua中的閉包,說它是一個(類似c++中的)類,它不是;
Lua中的閉包,說它是一個函數,它也不完全算是.只是它有點像c++中的函數,並且函數中有一些靜態局部變量一樣.但這樣理解也不完全對.因爲c++中的靜態局部變量
在整個程序生命週期內,僅一份實例(處於全局靜態存儲區中);
Lua中的閉包,其實是像c++中的lambda的,但它們也不能完全等價;
那麼Lua中的閉包到底像什麼?個人認爲它與c++中的仿函數是一樣的.因爲它們使用上都像函數,但又都可以持有自己的狀態屬性數據.而且又都可以產生任意多個的"實例"。
--]]