上述那些迭代器都有一个缺点,即需要为每个新的循环创建一个新的闭包。对于大多数情况而言,这或许不会有什么问题。例如,在之前的 allwords
迭代器中,创建一个闭包的开销相对于读取整个文件的开销而言几乎可以忽略不计。但是,在另外一些情况下,这样的开销可能会很客观。在这类情况中,我们可以通过使用泛型for自己保存迭代状态。
泛型for在循环过程中在其内部保存了迭代函数。实际上,泛型for保存了三个值:一个迭代函数,一个不可变状态和一个控制变量。
泛型for的语法如下:
for var-list in exp-list do body end
其中, var-list
是由一个或多个变量名组成的列表,以逗号分割; exp-list
是一个或多个表组成的列表,同样以逗号分割。通常,表达式列表只有一个元素,即一句对迭代器工厂的调用。
例如:在如下代码中,变量列表是 k
, v
,表达式列表只有一个元素 pairs(t)
:
for k, v in pairs(t) do print(k, v) end
我们把变量列表的第一个(或唯一的)变量称为控制变量(control variable),其值在循环过程中永远不会是 nil
,因为当其值为 nil
时循环就结束了。
for
做的第一件事情是对 in
后面的表达式求值。这些表达式应该返回三个值供 for
保存:迭代函数、不可变状态和控制变量的初始值。类似于多重赋值,只有最后一个(或唯一的)表达式能够产生不止一个值。表达式列表的结果只会保留三个,多余的值会被丢弃,不足三个则以 nil
补齐。例如,在使用简单迭代器时,工厂只会返回迭代函数,因此不可变状态和控制变量都是 nil
。
在上述的初始化步骤完成后, for
使用不可变状态和控制变量为参数来调用迭代函数。从 for
代码结构的立足点来看,不可变状态根本没有意义。 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 end
因此,假设迭代函数为 f
,不可变状态为 s
,控制变量的初始值为a0,那么在循环中控制变量的值依次为a1=f(s, a0),a2=f(s, a1),以此类推,直到ai为 nil
。如果 for
还有其他变量,那么这些变量只是简单的在每次调用 f
后得到额外的返回值。