lua for循環

在lua種,for語句有兩種形式

  • 數值型(numerical)
  • 泛型(generic)

數值型for:

基本語法以下閉包

for var exp1, exp2, exp3 do
    something
end

在循環開始以前,每一個表達式(exp1,exp2,exp3)都會執行一遍,循環開始後就再也不執行。函數

function exp1() print("exp1") return 1 end

function exp2() print("exp2") return 5 end

function exp3() print("exp3") return 1 end

for i = exp1(), exp2(), exp3() do
    print(i)
end

輸出:性能

exp1
exp2
exp3
1
2
3
4
5

var是被for自動聲明的局部變量,初始值爲exp1,做用範圍僅限於for循環內部。var的值小於等於exp2以前的每次循環中都會執行something,每次循環結束後都會將步長(exp3)增長到var上,exp3是可選的,若是沒有指定,默認步長爲1。若是想跳出循環可使用break。lua

若是想用for語句寫出一個無限循環,可與參考以下代碼:翻譯

for i = 1, math.huge do
    something
end

泛型for:

在瞭解泛型for以前咱們必須瞭解一下lua中的迭代器和閉包code

迭代器(iterator):

迭代器是一種可讓咱們遍歷一個集合中全部元素的代碼結構。在lua種一般使用函數表示迭代器:每次調用函數的時候,函數會返回集合中的下一個元素。ip

一個典型的例子就是io.read,每次調用的時候它都會返回標準輸入中的下一行,在沒有能夠讀取的行的時候返回nil。原型

閉包:

根據迭代器的概念咱們應該能想到,迭代器在連續調用當中應該保存一些狀態,這樣才能知道當前迭代器的位置以及如何從當前位置進步到下一個位置。對於自定義迭代器而言,閉包爲保存狀態提供了良好的機制。工作流

閉包就是一個能夠訪問自身環境中的局部變量的函數。it

這些局部變量記錄了迭代器連續調用過程當中的中間值,讓閉包能記住迭代器的位置。固然要建立一個新的閉包,咱們還須要非局部變量。所以,一個閉包一般包含兩個函數:

  1. 閉包自己
  2. 建立閉包和封裝變量的「工廠」

如下是一個簡單的table迭代器,它遍歷table返回全部的值

function values(t)          --建立閉包和封裝變量的「工廠」
    local i = 0
    print("create iter")
    return function()       --閉包自己
        i = i + 1
        print("call iter", i)
        return t[i]
    end
end

在這個例子中,values做爲工廠,每當它被調用的時候,就會建立一個新的閉包(迭代器自己)。這個閉包將自身的狀態保存在外部變量"t"和"i"中,這兩個變量是由values封裝的。每次調用迭代器,i的值加1,而後返回t[i],i做爲迭代器的外部變量,在迭代器返回後不會被清除,其值得以保存下來,起到了記錄迭代器位置的做用。在遍歷完table的最後一個元素後,迭代器返回nil,表示迭代結束。

咱們能夠在一個while循環中使用這個迭代器,它展現了迭代器的完整工做流程:

iter = values({1, 2, 3, 4})
while true do
    local v = iter()
    if not v then
        break
    end
    print(v)
end

輸出:

create iter
call iter       1
1
call iter       2
2
call iter       3
3
call iter       4
4
call iter       5

使用泛型for,咱們能夠以更簡潔的代碼實現上述邏輯。

for f in values({1, 2, 3, 4}) do
    print(f)
end

爲何泛型for能實現如此簡潔的代碼?下面咱們就來探究一下泛型for背後作了哪些事情。

泛型for的標準語法以下:

for var-list in exp-list do
    body
end
  • var-list:一個或多個變量組成的列表,由逗號分隔
  • exp-list:一個或多個表達式組成的列表,一樣由逗號分隔

咱們把變量列表(var-list)中的第一個變量稱爲控制變量(control variable),其值永遠不會是nil,由於當其值爲nil時循環已經結束了。

泛型for要作的第一件事就是對 exp-list 求值,這些表達式應該返回三個值供泛型for保存:

  1. 迭代函數
  2. 不可變狀態
  3. 控制變量初始值

表達式產生的返回值最多保留三個,不足三個則以nil補齊。例如上面的values()工廠只返回了迭代函數,因此不可變狀態和控制變量初始值均爲nil。用代碼表示上述過程就是:

-- _f 迭代函數
-- _s 不可變狀態
-- _v 控制變量初始值
local _f, _s, _v = exp-list

而後泛型for把_s和_v做爲參數調用_f,若是返回的第一個值(控制變量)爲nil,表示循環結束,不然,把_s和新的返回值做爲參數,繼續調用_f。其調用過程相似於數列,用僞代碼能夠表示爲:

local _v1, ... = _f(_s, _v)
local _v2, ... = _f(_s, _v1)
local _v3, ... = _f(_s, _v2)
...
local _vn, ... = _f(_s, _vn-1)

每次循環產生一次調用。這裏_f能夠返回多個值,可是隻有第一個返回值纔是控制變量,用來決定循環是否還要繼續。

因此一個形如:

for var_1, var_2, ... in explist do something end

的泛型for語句能夠翻譯爲:

local _f, _s, _v = exp-list
while true do
    local var_1, var_2, ... = _f(_s, _v)
    _v = var_1
    if not _v then break end
    something
end

與上面最原始的迭代器相比較,這裏的迭代函數_f有一個顯著的區別,就是_f能夠接收參數了,而且參數的值就包含了當前迭代器的狀態,也就是說迭代器自身不須要保存狀態了。所以誕生了一個新的概念:

無狀態迭代器

顧名思義,無狀態迭代器就是一種不須要保存任何狀態的迭代器。所以在多個循環中可使用同一個迭代器,從而避免了建立閉包的開銷,讓代碼在性能上獲得了提高。ipairs就是一個典型的無狀態迭代器。例如:

for i, v in ipairs({"hello", "lua", "for"}) do
    print(i, v)
end

咱們本身能夠用lua實現一個ipairs迭代器

local function my_iter(t, i)
    print("call:"..i)
    i = i + 1
    local v = t[i]
    if v then 
        return i, v
    end
end

function my_ipairs(t)
    print("init")
    return my_iter, t, 0
end

for i, v in my_ipairs({"hello", "lua", "for"}) do
    print(i, v)
end

輸出:

init
call:0
1    hello
call:1
2    lua
call:2
3    for
call:3

在上述示例中,調用for循環的第一步就是對my_ipairs求值,_f, _s, _v分別得到返回值:

_f = my_iter
_s = t
_v = 0

在第一次循環中執行:

i, v = _f(t, _v)  --等價於 i, v = my_iter(t, 0)

執行結果爲:

i = 1
v = t[1]

緊接着第二次循環,泛型for會把第一次循環中_f(t, _v)的第一個返回值(i)做爲控制變量,進行第二次調用:

i, v = _f(t, _v)  --等價於 i, v = my_iter(t, 1)

執行結果爲:

i = 2
v = t[2]

按照此規律進行迭代,直至i的值爲nil。

pairs函數也是一個無狀態迭代器,它調用的是lua中一個基本的迭代函數:next(t, k)。pairs的原型能夠描述以下:

function pairs(t)
    return next, t, nil
end

next(t, k)的返回值有兩個:

  1. 隨機次序返回k的下一個鍵,當key==nil的時候返回表中的第一個鍵,當全部表便利完畢,返回nil
  2. k對應的值,當k==nil的時候返回表中的第一個值

所以咱們能夠不使用pairs而直接調用next,同樣能夠實現遍歷效果

for i, v in next, {"hello", "lua", "for"} do
    print(i, v)
end

總結:

  1. lua中for循環有兩種,數值類for泛型for。
  2. 迭代器也有兩種,有狀態的和無狀態的。只有在for循環中才能實現無狀態迭代器。
相關文章
相關標籤/搜索