Lua-迭代器

1. 迭代器与闭包的结合

迭代器是用于遍历集合元素的机制,在 Lua 中通常以函数 + 闭包的形式实现。闭包可以 "记住" 遍历的状态(如当前位置、已读内容等),从而在每次调用时返回 "下一个元素"。

  • 示例:为列表创建返回元素值的迭代器

    Lua 复制代码
    function values(t)
      local i = 0
      return function()  -- 闭包:保存状态变量 i
        i = i + 1
        return t[i]
      end
    end

    这里 values 是 "迭代器工厂",每次调用会创建一个闭包,闭包通过 i 记录遍历位置,每次调用返回列表的下一个元素,直到返回 nil 表示遍历结束。

2. 泛型 for 的语义与工作机制

泛型 for 是 Lua 专门为迭代器设计的语法糖,它会自动处理迭代器的调用和状态管理,简化遍历逻辑。

  • 语法形式:for <var-list> in <exp-list> do <body> end

  • 工作原理:泛型 for 会先对 <exp-list> 求值,期望得到三个值迭代器函数恒定状态控制变量初始值 。随后,for 会反复调用迭代器函数,将返回值赋值给 <var-list>,直到迭代器返回 nil 时结束循环。

  • 等价逻辑(帮助理解):

    Lua 复制代码
    -- 泛型 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

3. 无状态迭代器

与依赖闭包保存状态的迭代器不同,无状态迭代器自身不保存遍历状态 ,而是通过泛型 for 传递的 "恒定状态" 和 "控制变量" 来确定下一个元素。

  • 示例:ipairs 就是典型的无状态迭代器,它通过 "恒定状态(数组本身)" 和 "控制变量(当前索引)" 来遍历数组:

    Lua 复制代码
    a = {"one", "two", "three"}
    for i, v in ipairs(a) do
      print(i, v)
    end

"无状态迭代器" 是 Lua 中一种特殊的迭代器,核心特点是自身不保存任何遍历状态 (比如当前位置、进度等),所有状态都通过泛型 for 循环来传递和维护。这听起来有点抽象,我们用具体例子和对比来理解:

先看一个 "有状态迭代器"(依赖闭包保存状态)

之前讲的计数器迭代器就是典型的 "有状态",它用闭包中的变量(比如 i)保存当前遍历位置:

Lua 复制代码
-- 有状态迭代器:用闭包的 i 保存遍历位置
function iter(t)
  local i = 0  -- 状态变量(保存在闭包中)
  return function()
    i = i + 1
    return t[i]
  end
end

-- 使用:每次调用迭代器,都依赖闭包中的 i 推进
local t = {"a", "b", "c"}
local it = iter(t)  -- 创建迭代器(闭包)
print(it())  --> a(i=1)
print(it())  --> b(i=2)
print(it())  --> c(i=3)

这里的 "状态"(i)被闭包 "记住",迭代器自己知道下一步该遍历哪里。

再看 "无状态迭代器":状态由 for 循环传递

无状态迭代器不自带状态 ,它的每次调用都需要外部提供 "当前状态",并返回 "下一个状态"。最典型的例子就是 ipairs(用于遍历数组):

Lua 复制代码
-- 模拟 ipairs 的无状态迭代器实现
function my_ipairs(t, i)  -- t 是数组(恒定状态),i 是当前索引(变化的状态)
  i = i + 1  -- 从当前索引推进到下一个
  local v = t[i]  -- 获取下一个值
  if v then  -- 如果存在值,返回索引和值(下一个状态)
    return i, v
  end  -- 否则返回 nil,结束循环
end

-- 泛型 for 循环使用无状态迭代器
local t = {"a", "b", "c"}
-- for 循环会自动传递状态:初始 i=0,每次调用 my_ipairs(t, i)
for i, v in my_ipairs, t, 0 do
  print(i, v)
end

输出:

复制代码
1   a
2   b
3   c

无状态迭代器的核心逻辑:

  1. 三个关键角色

    • 迭代器函数 (如 my_ipairs):负责计算下一个元素,接收两个参数 ------恒定状态(不会变的,比如数组 t)和 当前状态(会变的,比如索引 i)。
    • 恒定状态 (如 t):整个遍历过程中不变的量,通常是被遍历的集合本身。
    • 当前状态 (如 i):记录遍历进度,每次迭代后更新,作为下一次调用的参数。
  2. 泛型 for 的自动协作

    • 第一次调用:for 会用 恒定状态(t)初始状态(0) 调用迭代器,得到 i=1, v=a
    • 第二次调用:for 会用 恒定状态(t)上一次返回的 i=1 调用迭代器,得到 i=2, v=b
    • 直到迭代器返回 nil,循环结束。
  3. 为什么叫 "无状态" :迭代器函数(my_ipairs)本身不保存任何状态(没有闭包变量、全局变量记录 i),所有状态(i)都由 for 循环传递,每次调用都是 "无记忆" 的,完全依赖输入的 i 来计算下一步。

无状态迭代器的优势:

  • 更轻量:不需要闭包来保存状态,减少内存占用。
  • 可重入性:同一个迭代器函数可以同时用于多个遍历(因为状态由外部传递,互不干扰)。

Lua 中的 ipairs(遍历数组)和 pairs(遍历表)都是无状态迭代器的典型应用,理解了这个逻辑,就能明白为什么泛型 for 能简洁地遍历各种集合了~

相关推荐
2501_941111771 天前
C++代码移植性设计
开发语言·c++·算法
~无忧花开~1 天前
Vue.config.js配置全攻略
开发语言·前端·javascript·vue.js
脉动数据行情1 天前
Go语言对接股票、黄金、外汇API实时数据教程
开发语言·后端·golang
橘子真甜~1 天前
C/C++ Linux网络编程5 - 网络IO模型与select解决客户端并发连接问题
linux·运维·服务器·c语言·开发语言·网络·c++
霖001 天前
ZYNQ——ultra scale+ IP 核详解与配置
服务器·开发语言·网络·笔记·网络协议·tcp/ip
flypwn1 天前
justCTF 2025JSpositive_player知识
开发语言·javascript·原型模式
oliveira-time1 天前
原型模式中的深浅拷贝
java·开发语言·原型模式
2501_941111461 天前
C++中的原型模式
开发语言·c++·算法
亿坊电商1 天前
PHP框架的资源管理机制如何优雅适配后台任务?
开发语言·php
VBA63371 天前
YZ系列工具之YZ09: VBA_Excel之读心术
开发语言