迭代器和闭包

简介: 迭代器和闭包

迭代器iterator )是一种可以让我们遍历一个集合中所有元素的代码结构。在 Lua 语言中,通常使用函数表示迭代器:每一次调用函数时,函数会返回集合中的“下一个”元素。一个典型的例子就是 io.read ,每次调用该函数时它都会返回标准输入中的下一行,在没有可以读取的行时返回 nil


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


作为示例,让我们来为列表编写一个简单的迭代器。与 ipairs 不同的是,该迭代器并不是返回每个元素的索引值而是返回元素的值:

function values (t)
  local i = 0
  return function () i = i +1; return t[i] end
end


在这个例子中, values 就是工厂。每当调用这个工厂时,它就会创建一个新的闭包(即迭代器本身)。这个闭包将它的状态保存在其外部的变量 ti 中,这两个变量也是由 values 创建的。每次调用这个迭代器时,它就从列表 t 中返回下一个值。在遍历完最后一个元素后,迭代器返回 nil ,表示迭代结束。


我们可以在一个 while 循环中使用这个迭代器:

t = {10, 20, 30}
iter = values(t)                      -- 创建迭代器
while true do
  local element = iter()              -- 调用迭代器
  if element == nil then break end
  print(element)
end


不过,使用泛型for更简单。毕竟,泛型for正是为了这种迭代而设计的:

t = {10, 20, 30}
for element in values(t) do
  print(element)
end


泛型for为一次迭代循环做了所有的记录工作:它在内部保存了迭代函数,因此不需要变量 iter ;它在每次做新的迭代时都会再次调用迭代器,并在迭代器返回 nil 时结束循环。


来看一个更高级的示例,这个示例展示了一个迭代器,它可以遍历来自标准输入的所有单词:

function allwords()
  local line = io.read()                                    -- 当前行
  local pos = 1                                             -- 当前行的当前位置
  return function()                                         -- 迭代函数
    while line do                                           -- 当还有行时循环
      local w, e = string.match(line, "(%w+)()", pos)       
      if w then                                             -- 发现一个单词?
        pos = e                                             -- 下一个位置位于该单词后
        return w                                            -- 返回该单词
      else
        line = io.read()                                    -- 没找到单词,尝试下一行
        pos = 1                                             -- 从第一个位置重新开始
      end
    end
    return nil                                              -- 没有行了,迭代结束
  end
end


为了完成这样的遍历,我们需要保存两个值:当前行的内容(遍历 line )及当前行的当前位置(遍历 pos )。有了这些数据,我们就可以不断产生下一个单词。这个迭代函数的主要部分是调用函数 string.match ,以当前位置作为起始在当前行中搜索一个单词。函数 string.match 使用模式 '%w+' 来匹配一个单词,也就是匹配一个或多个字母/数字字符。如果函数 string.match 找到了一个单词,它就捕获并返回这个单词及该单词之后的第一个字符的位置(一个空匹配),迭代函数则更新当前位置并返回该单词,否则,迭代函数读取新的一行,然后重复上述搜索过程。在所有的行都被读取完后,迭代函数返回 nil 以表示迭代结束。


尽管迭代器本身有点复杂,但allwords的使用还是很简明易懂的:

for word in allwords() do
  print(word)
end


对于迭代器而言,一种常见的情况就是:编写迭代器可能不太容易,但使用迭代器却十分简单。这也不是一个大问题,因为使用 Lua 语言编程的最终用户一般不会取定义迭代器,而只会使用那些宿主应用已经提供的迭代器。

目录
相关文章
|
3月前
|
自然语言处理 JavaScript 前端开发
什么是闭包
【10月更文挑战第12天】什么是闭包
|
5月前
|
存储 自然语言处理 JavaScript
闭包
闭包
25 0
|
8月前
|
C++ 容器
C++:迭代器
C++:迭代器
98 0
|
8月前
|
算法 C++ 容器
c++迭代器介绍
C++中的迭代器是一种抽象的数据访问对象,它允许对容器中的元素进行遍历,而不必暴露底层数据结构的细节。迭代器提供了一种通用的方法来访问容器中的元素,无论容器的类型是什么。C++标准库中的许多容器(如vector、list、map等)都支持迭代器。
96 0
|
存储 缓存 前端开发
详解 Reat 中的闭包问题
JavaScript 中的闭包一定是最可怕的特性之一。 即使是无所不知的 ChatGPT 也会告诉你这一点。 它也可能是最隐秘的语言概念之一。 每次编写任何 React 代码时,我们都会用到它,大多数时候我们甚至没有意识到。 但最终还是无法摆脱它们:如果我们想编写复杂且高性能的 React 应用程序,我们就必须了解闭包。
113 0
详解 Reat 中的闭包问题
|
算法 区块链 C++
迭代器与仿函数
迭代器与仿函数
|
设计模式 开发框架 .NET
C#——迭代器
C#——迭代器
92 0
C#——迭代器
闭包的使用
闭包的使用
77 0
6、什么是闭包
6、什么是闭包
103 0