迭代器就是一種可以遍歷一種集合中所有元素的機制,在Lua中,通常將迭代器表示為函數。每調用一次函數,就返回集合中的“下一個”元素。每個迭代器都需要在每次成功調用之後保存一些狀態,這樣才能知道它所在的位置及如何走到下一個位置,通過之前博文的總結,閉包對於這樣的任務提供了極佳的支持。現在我們就用代碼來實現一個簡單的迭代器。
function values(tb) local i = 0 return function () i = i + 1 return tb[i] endendlocal testTb = {10, 20, 30}for value in values(testTb) do print(value)end
這就是一個最簡單的迭代器,使用閉包來完成整個任務;這只是一個簡單的例子,接下來,再看看泛型for的語義。
泛型for比較復雜,它在循環過程內保存了迭代器函數。它實際上保存著3個值:一個迭代器函數、一個恆定狀態和一個控制變量。接下來,分別進行總結。
泛型for的語法如下:
for <var-list> in <exp-list> do <body>end
其中,<var-list>是一個或多個變量名的列表,以逗號分隔;<exp-list>是一個或多個表達式的列表,同樣以逗號分隔。通常表達式列表只有一個元素,即一句對迭代器函數的調用。例如:
for k, v in pairs(t) do print(k, v) end
for做的第一件事就是對in後面的表達式求值,這些表達式應該返回3個值供for保存:迭代器函數、恆定狀態和控制變量的初值。這裡和多重賦值是一樣的,只有最後一個表達式才會產生多個結果,並且只會保留前3個值,多余的值會被丟棄;而不夠的話,就以nil補足。
在初始化完成以後,for會以恆定狀態和控制變量來調用迭代器函數。然後for將迭代器函數的返回值賦予變量列表中的變量。如果第一個返回值為nil,那麼循環就終止,否則,for執行它的循環體,隨後再次調用迭代器函數,並重復這個過程。在前言部分的代碼中,只是返回了迭代器函數,並沒有返回恆定狀態和控制變量。下面通過代碼來說明這個問題,比如:
for var_1, ..., var_n in <explist> do <block> end -- 就等價於以下代碼:do local _f, _s, _var = <explist> -- 返回迭代器函數、恆定狀態和控制變量的初值 while true do local var_1, ..., var_n = _f(_s, _var) _var = var_1 if _var == nil then break end <block> end endend
所謂“無狀態的迭代器”,就是一種自身不保存任何狀態的迭代器。因此,我們可以在多個循環中使用同一個無狀態的迭代器,避免創建新的閉包的開銷。
在每次迭代中,for循環都會用恆定狀態和控制變量來調用迭代器函數。一個無狀態的迭代器可以根據這兩個值來為下次迭代生成下一個元素。這類迭代器的代表就是ipairs。它可以用來迭代一個數組的所有元素。如下述演示代碼:
local aTb = {"One", "Two", "Three"}for i, v in ipairs(aTb) do print(i, v)end
在這裡,迭代器狀態就是需要遍歷的table(一個恆定狀態,它不會在循環中改變)及當前的索引值(控制變量)。我們可以使用Lua代碼來實現ipairs,大概就如下代碼:
local function iter(a, i) i = i + 1 local v = a[i] if v then return i, v endendfunction ipairs(a) return iter, a, 0end
函數pairs與ipairs類似,也是用於遍歷一個table中的所有元素。不同的是,它的迭代器函數是Lua中的一個基本函next。
function pairs(a) return next, t, nilend
在調用next(t, k)時,k是table t的一個key。此調用會以table中的任意次序返回一組值:此table的下一個key,及這個key所對應的值。而調用next(t, nil)時,返回table的第一組值。若沒有下一組值時,next返回nil。所以,我們也可以使用next來判斷一個table是否為空。
對於大家經常迷惑的ipairs和pairs的區別,在這裡就能看的一清二楚了,ipairs只能用於遍歷index是整型的table,同時,由於ipairs返回的控制變量初值為0,這就決定了,ipairs只能訪問index從1開始的key和value;ipairs不能返回nil,當key對應的值為nil時,就直接終止遍歷;而pairs則沒有要求。關於ipairs和pairs的具體差異,請參考這篇博文:點這裡。
當然了,有了無狀態的迭代器,就有了有狀態的迭代器了,有狀態的迭代器就是專門用一個table來保存狀態;在無狀態的迭代器中,我們每一次都是迭代一個table,這個table就是一個無狀態的table,它不會再遍歷的過程中發生變化,而有狀態的迭代器,則會在遍歷的過程中對迭代的table進行變更,迭代的table的狀態也隨之發生了變化。這裡不做詳細的總結。