关于Lua中的迭代器:ipairs与pairs。

迭代器和泛型for:

迭代器和闭包:

所有的迭代器都需要在连续的调用之间保存一些状态,这样才能知道当前迭代所处的位置及如何从当前位置步进到下一位置。对于我们自己的迭代器而言,闭包为保存状态提供了一种良好的机制。一个闭包就是一个可以访问其自身的环境中一个或多个局部变量的函数。这些变量将连续调用过程中的值并将其保存在闭包中,从而使得闭包能够记住迭代所处的位置。当然,要创建一个新的闭包,我们还需要创建非局部变量。因此,一个闭包结构通常涉及两个函数:闭包本身和一个用于创建该闭包及其封装变量的工厂。

泛型for:

泛型for在循环过程中在其内部保存了迭代函数,泛型for保存了三个值:一个迭代函数、一个不可变状态和一个控制变量。

语法如下:

1
2
3
for var-list in exp-list do
body
end

其中,var-list是由一个或多个变量名组成的列表,以逗号分隔;exp-list是一个或多个表达式组成的列表,同样以逗号分隔。通常,表达式列表只有一个元素,即一句对迭代器工厂的调用。

1
for k,v in pairs(t) do print(k,v) end

我们把变量列表的第一个变量称为控制变量,其值在循环过程中永远不会为nil,因为当其值为nil时循环就结束了。

for做的第一件事情是对in后面的表达式求值。这些表达式应该返回三个值供for保存:迭代函数、不可变状态和控制变量的初始值。类似于多重赋值,只有最后一个白哦大事能够产生不止一个值;表达式列表的结果只会保留三个,多余的值会被丢弃,不足三个则以nil补齐。

在上述的初始化步骤完成后,for使用不可变状态和控制变量为参数来调用迭代函数,从for代码结构的立足点来看,不可变状态根本没有意义,for只是把从初始化步骤得到的状态值传递给所有迭代函数,然后,for将迭代函数的返回值赋给变量列表中声明的变量。如果第一个返回值为nil,那么循环终止;否则,for执行它的循环体并再次调用迭代函数,再不断的重复这个过程。

两种语法等价:

1
for var_1,...,var_n in explist do block end

等价于:

1
2
3
4
5
6
7
8
9
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
end

假设迭代函数是f,不可变状态为s,控制变量的初始值为a0,那么在循环中控制变量的值依次为a1=f(s,a0),a2=f(s,a1),直到ai为nil。如果for还有其他变量,那么这些变量只是简单的在每次调用f后得到额外的返回值。

无状态迭代器:

无状态迭代器就是一种自身不保存任何状态的迭代器,因此,可以在多个循环中使用同一个无状态迭代器,从而避免创建新闭包的开销。

for循环会以不可变状态和控制变量为参数调用迭代函数,一个无状态迭代器之需要这两个值来为迭代生成下一个元素:

1
2
3
4
a = {"One","Two","Three"}
for i,v in ipairs(a) do
print(i,v)
end

ipairs和迭代器都非常简单:

1
2
3
4
5
6
7
8
9
10
11
local function (t,i)
i = i + 1
local v = t[i]
if v then
return i,v
end
end

function ipairs(t)
return iter,t,0
end

当调用for循环中的ipairs(t)时,ipairs(t)会返回三个值,即迭代函数iter,不可变状态表t和控制变量的初始值0。

然后Lua语言调用iter(t,0),得到1,t[1](除非t[1]已经变成了nil)。在第二次迭代中,Lua语言调用iter(t,1),得到2,t[2],依次类推,直至得到第一个为nil的元素。

函数pairs和函数ipairs类似,也用于遍历一个表中的所有元素,不同的是,函数pairs的迭代函数是Lua语言中的一个基本函数next:

1
2
3
function pairs(t)
return next,t,nil
end

在调用next(t,k)时,k是表t的一个键,该函数会以随机次序返回表中的下一个键以及k对应的值,调用next(t,nil)时,返回表中的第一个键值对。当所有的元素遍历完时,函数next返回nil。

区别:

  • pairs:用于迭代table,可以遍历表中所有的key,可以返回nil
  • ipairs: 迭代数组,不能返回 nil,如果遇到 nil 则退出