《lua程序设计》读书笔记 第七章:迭代器与泛型for

来源:互联网 发布:宁波软件外包 编辑:程序博客网 时间:2024/06/13 01:31

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    endendfor 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>    endend

当我们使用一个简单的迭代器时,工厂只返回一个迭代器函数,因此恒定状态和控制变量就是nil了。

7.3 无状态的迭代器

所谓无状态的迭代器,就是自身不保留任何状态的迭代器,在每次迭代时,for循环会用恒定状态和控制变量来调用迭代器函数来生成下一个元素。典型例子就是ipairs。
自己实现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

7.4 具有复杂状态的迭代器

同行迭代器需要保存很多状态,可是for循环只提供了一个恒定状态和一个控制变量。一个简单的解决方法就是使用closure,或者还可以将迭代器所需的所有状态打包为一个table,保存在恒定状态中。

local iteratorfunction allwords()    local state = {line = io.read(), pos = 1}    return iterator, stateendfunction 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 nilend

尽可能编写无状态的迭代器,那样所有状态保存在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    endend

两种迭代器有大致相同的开销,但是生成器风格的迭代器更加灵活。

原创粉丝点击