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 能简洁地遍历各种集合了~

相关推荐
安冬的码畜日常3 小时前
【JUnit实战3_11】第六章:关于测试的质量(下)
junit·单元测试·tdd·1024程序员节·bdd·变异测试
渡我白衣3 小时前
C++ 同名全局变量:当符号在链接器中“相遇”
开发语言·c++·人工智能·深度学习·microsoft·语言模型·人机交互
淮北4943 小时前
html + css +js
开发语言·前端·javascript·css·html
源码_V_saaskw4 小时前
JAVA国际版二手交易系统手机回收好物回收发布闲置商品系统源码支持APP+H5
java·开发语言·微信·智能手机·微信小程序·小程序
java1234_小锋4 小时前
PyTorch2 Python深度学习 - PyTorch2安装与环境配置
开发语言·python·深度学习·pytorch2
海边夕阳20065 小时前
深入解析volatile关键字:多线程环境下的内存可见性与指令重排序防护
java·开发语言·jvm·架构
ZeroKoop5 小时前
JDK版本管理工具JVMS
java·开发语言
乾坤瞬间5 小时前
【Java后端进行ai coding实践系列二】记住规范,记住内容,如何使用iflow进行上下文管理
java·开发语言·ai编程