7.1 迭代器与closure
“迭代器”就是一种遍历一种集合中所有元素的机制,在Lua中,通常将迭代器表示为函数,没调用一次函数,便返回集合中的“下一个”元素。closure为迭代器提供了绝佳的支持,一个closure结构通常涉及到两个函数:closure本身和一个用于创建closure的工厂函数。
我们编写一个简单的迭代器,返回table中每个元素的值:
function value(t)
local i = 0
return function() i = i + 1; return t[i] end
end
在本例中,value是一个工厂,它将返回一个闭包closure。使用该迭代器遍历一个table如下:
t = {10, 20, 30}
iter = value(t)
while true do
local element = iter()
if element == nil then break end
print(element)
end
然而使用泛型for则更为简单,它正是为迭代器而设计的:
t = {10, 20, 30}
for val in value(t) do
print(val)
end
再看一个稍微复杂点的示例:
function allwords()
local line = io.read()
local pos = 1
return function()
while line do
local s,e = string.find(line, "%w+", pos)
if s then
pos = e + 1
return string.sub(line, s, e)
else
line = io.read()
pos = 1
end
end
return nil
end
end
for word in allwords() do
print(word)
end
7.2 泛型for的语义
前面提到的迭代器都有一个缺点,就是需要为每个新的循环都创建一个closure。
泛型for在循环过程内部保存了迭代器函数,实际上它保存了3个值:一个迭代器函数、一个恒定状态和一个控制变量。泛型for语法如下:
for <var-list> in <exp-list> do
< body >
end
< var-list > 是一个或多个变量名的列表,以逗号分隔;< exp-list > 是一个或多个表达式的列表,同样以逗号分隔。通常表达式列表只有一条语句,即对迭代器工厂的调用。变量列表的第一元素为“控制变量”。在循环过程中该值绝不会为nil,因为当它为nil时循环就结束了。
for做的第一件事情是对in后面的表达式求值。这些表达式应该返回3个值供for保存:迭代器函数、恒定状态、控制变量的初值。这里有点类似多重赋值,即只有最后的表达式才会产生多个结果,且最多保留3个值。
明确的说,以下语句:for var_1, …, val_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
当我们使用一个简单的迭代器时,工厂只返回一个迭代器函数,因此恒定状态和控制变量就是nil了。
7.3 无状态的迭代器
所谓无状态的迭代器,就是自身不保留任何状态的迭代器,在每次迭代时,for循环会用恒定状态和控制变量来调用迭代器函数来生成下一个元素。典型例子就是ipairs。
自己实现ipairs如下:
local function iter(a, i)
i = i + 1
local v = a[i]
if v then return i, v end
end
function ipairs(a)
return iter, a, 0
end
7.4 具有复杂状态的迭代器
同行迭代器需要保存很多状态,可是for循环只提供了一个恒定状态和一个控制变量。一个简单的解决方法就是使用closure,或者还可以将迭代器所需的所有状态打包为一个table,保存在恒定状态中。
local iterator
function allwords()
local state = {line = io.read(), pos = 1}
return iterator, state
end
function iterator(state)
while state.line do
local s, e = string.find(state.line, "%W+", state.pos)
if s then
state.pos = e + 1
return string.sub(state.line, s, e)
else
state.line = io.read()
state.pos = 1
end
end
return nil
end
尽可能编写无状态的迭代器,那样所有状态保存在for变量中,不需要在开始一个循环时创建新的对象。否则,应该尝试使用closure,因为通常一个closure的实现的迭代器比一个使用table的迭代器更加高效,这是因为一个closure比一个table更廉价,其次访问“非局部的变量”比访问table字段更快。
7.5 真正的迭代器
实际上上述所说的迭代器并没有做实际的迭代,真正做迭代的是for循环。而迭代器只是为每次迭代提供一些成功后的返回值,其更像是一个“生成器”。还有一种创建迭代器的方式是,在迭代器中做实际的迭代操作:
function allwords(f)
for line in io.lines() do
for word in string.gmatch(line, "%W+") do
f(word)
end
end
end
两种迭代器有大致相同的开销,但是生成器风格的迭代器更加灵活。