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

相关推荐
火山灿火山8 小时前
Qt常用控件(五) - 多元素控件
开发语言·qt
熬了夜的程序员8 小时前
【Rust学习之路】序
开发语言·后端·学习·rust
say_fall8 小时前
C语言编程实战:每日一题:用栈实现队列
c语言·开发语言
deng-c-f8 小时前
C/C++内置库函数(4):c++左右值及引用的概念、move/forward的使用
c语言·开发语言·c++
零雲8 小时前
java面试:怎么保证消息队列当中的消息丢失、重复问题?
java·开发语言·面试
冬夜戏雪8 小时前
【java学习日记】【12.11】【11/60】
java·开发语言
在坚持一下我可没意见8 小时前
Spring 后端安全双剑(下篇):JWT 无状态认证 + 密码加盐加密实战
java·开发语言·spring boot·后端·安全·spring
deng-c-f9 小时前
C/C++内置库函数(3):future、promise的用法
c语言·开发语言·c++
2501_921649499 小时前
亚太股票数据API:日股、韩股、新加坡股票、印尼股票市场实时行情,实时数据API-python
开发语言·后端·python·websocket·金融
chaodaibing9 小时前
【Java】一个批量更新插入数据到MySQL的工具类
java·开发语言·mysql