閉包函式到底是什麼?有什麼用?

staryinworld發表於2017-05-11

轉載請註明出處即可,無需經過我本人同意。
以下內容為網上的內容加上本人的理解,如有侵權,請通知本人刪除文章。

閉包函式是什麼:

首先看如下程式碼,你是否瞭解其真正的意義:

function test()
        local i=0
        return function()
            i=i+1
            return i
        end
end

doTest=test()
print(doTest())   --輸出1
print(doTest())   --輸出2

你可能有這樣的疑問:
1. 呼叫test()返回的函式doTest()時,變數 i 的定義在哪?
2. test()中的i不是區域性變數嗎?為什麼返回的函式還能呼叫。

如果你有這樣的疑問,請接著往下看。
首先,瞭解幾個概念:

詞法定界:當一個函式內巢狀另一個函式的時候,內函式可以訪問外部函式的區域性變數,這種特徵叫做詞法定界。而這些變數就被稱為該內嵌函式的upvalue,upvalue實際指的是變數而不是值,這些變數可以在內部函式之間共享

閉包:通過呼叫含有一個內部函式加上該外部函式持有的外部區域性變數(upvalue)的外部函式(外部函式就是工廠)產生的一個例項函式。

閉包組成:外部函式+外部函式建立的upvalue+內部函式(閉包函式)

如上面的函式test(),
test()就是外部函式
外部函式的區域性變數local i=0就是upvalue(也叫做非區域性變數,之所以叫做非區域性變數,是因為此變數的作用域既不是區域性變數的作用域,也不是全域性變數的作用域。),
返回的函式就是內部函式

現在再回頭看看原來的函式,我們知道了這種形式的函式叫做閉包函式。而test()中的區域性變數i是內嵌函式的upvalue(非區域性變數),且在內部函式中共享。

重複呼叫內部函式時,每一個呼叫都會記住上一次呼叫後的值,就是說第一次呼叫doTest()之後,i 的值已經是1了。

下面再看個例子

function test()
        local i=0
        return function()
            i=i+1
            return i
        end
end

doTest=test()
doAgain=test()
print(doTest())   --輸出1
print(doTest())   --輸出2

print(doAgain())  --輸出1
print(doAgain())  --輸出2

可以看到,此時執行doAgain()時 i 值並沒有在原來的基礎上增加。

原因是:
doTest,doAgain是建立在同一個函式,同一個區域性變數的不同例項上面的兩個不同的閉包
呼叫一次test()就會產生一個新的閉包, 而閉包中的upvalue各自獨立。所以不難解釋為什麼doAgain()的i 值為什麼沒有在原來的基礎上增加了。

閉包函式有什麼用:

在for in 的迴圈中需要使用到迭代器,而迭代器需要保留上一次呼叫的狀態和下一次成功呼叫的狀態,剛好可以使用閉包的機制來實現。

建立迭代器:

function list_iter(t)     --外包函式叫做工廠函式。
            local i=0
            local n=table.getn(t)
            return function()
                i=i+1
                if i<=n then return t[i] end
            end
    end
--[[這裡的list_iter是一個工廠,每次呼叫都會產生一個新的閉包。該閉包內部包括了upvalue(t,i,n)。
因此每呼叫一次該函式產生的閉包,那麼該閉包就會根據記錄上一次的狀態,以及返回list的下一個。]]

在while中使用:

--while中使用:
t={10,20,90}
iter=list_iter(t)  --呼叫迭代器產生一個閉包
while true do
--當閉包函式的i值已經等於n的值時,依然會執行閉包函式,此時返回的就是nil.
--如果沒有下面的判斷,while就會一直迴圈,並進入死迴圈。
    local element=iter()
    if element==nil then break end
    print(element)
end

在泛型for使用


--泛型for使用:
t={10,0,29}
--這裡的list_iter()工廠函式只會被呼叫一次產生一個閉包函式,
--後面的每一次迭代都是用該閉包函式,而不是工廠函式。
for element in list_iter(t) do
    print(element)
end

如果想要同時返回k,v值,需要修改工廠函式,如下所示:

function list_iter(tb)
     local i = 0
     return function ()
          i = i + 1
           --如果沒有下面這個判斷,就會一直執行。
           --詳情可以看我的另一篇部落格,《Lua內容關於for迴圈的總結》
          if tb[i] == nil then  
            return nil
          end   
          return i,tb[i]
     end
end

相關文章