Lua-迭代器
1. 迭代器与闭包的结合
迭代器是用于遍历集合元素的机制,在 Lua 中通常以函数 + 闭包的形式实现。闭包可以 “记住” 遍历的状态(如当前位置、已读内容等),从而在每次调用时返回 “下一个元素”。
- 示例:为列表创建返回元素值的迭代器
这里function values(t)local i = 0return function() -- 闭包:保存状态变量 ii = i + 1return t[i]end endvalues是 “迭代器工厂”,每次调用会创建一个闭包,闭包通过i记录遍历位置,每次调用返回列表的下一个元素,直到返回nil表示遍历结束。
2. 泛型 for 的语义与工作机制
泛型 for 是 Lua 专门为迭代器设计的语法糖,它会自动处理迭代器的调用和状态管理,简化遍历逻辑。
- 语法形式:
for <var-list> in <exp-list> do <body> end - 工作原理:泛型
for会先对<exp-list>求值,期望得到三个值:迭代器函数、恒定状态、控制变量初始值。随后,for会反复调用迭代器函数,将返回值赋值给<var-list>,直到迭代器返回nil时结束循环。 - 等价逻辑(帮助理解):
-- 泛型 for 语法 for var_1, ..., var_n in <explist> do <block> end-- 等价的手动实现 dolocal _f, _s, _var = <explist> -- 分别对应:迭代器函数、恒定状态、控制变量初始值while true dolocal var_1, ..., var_n = _f(_s, _var)_var = var_1if _var == nil then break end<block>end end
3. 无状态迭代器
与依赖闭包保存状态的迭代器不同,无状态迭代器自身不保存遍历状态,而是通过泛型 for 传递的 “恒定状态” 和 “控制变量” 来确定下一个元素。
- 示例:
ipairs就是典型的无状态迭代器,它通过 “恒定状态(数组本身)” 和 “控制变量(当前索引)” 来遍历数组:a = {"one", "two", "three"} for i, v in ipairs(a) doprint(i, v) end
“无状态迭代器” 是 Lua 中一种特殊的迭代器,核心特点是自身不保存任何遍历状态(比如当前位置、进度等),所有状态都通过泛型 for 循环来传递和维护。这听起来有点抽象,我们用具体例子和对比来理解:
先看一个 “有状态迭代器”(依赖闭包保存状态)
之前讲的计数器迭代器就是典型的 “有状态”,它用闭包中的变量(比如 i)保存当前遍历位置:
-- 有状态迭代器:用闭包的 i 保存遍历位置
function iter(t)local i = 0 -- 状态变量(保存在闭包中)return function()i = i + 1return 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(用于遍历数组):
-- 模拟 ipairs 的无状态迭代器实现
function my_ipairs(t, i) -- t 是数组(恒定状态),i 是当前索引(变化的状态)i = i + 1 -- 从当前索引推进到下一个local v = t[i] -- 获取下一个值if v then -- 如果存在值,返回索引和值(下一个状态)return i, vend -- 否则返回 nil,结束循环
end-- 泛型 for 循环使用无状态迭代器
local t = {"a", "b", "c"}
-- for 循环会自动传递状态:初始 i=0,每次调用 my_ipairs(t, i)
for i, v in my_ipairs, t, 0 doprint(i, v)
end
输出:
1 a
2 b
3 c
无状态迭代器的核心逻辑:
三个关键角色:
- 迭代器函数(如
my_ipairs):负责计算下一个元素,接收两个参数 ——恒定状态(不会变的,比如数组t)和当前状态(会变的,比如索引i)。 - 恒定状态(如
t):整个遍历过程中不变的量,通常是被遍历的集合本身。 - 当前状态(如
i):记录遍历进度,每次迭代后更新,作为下一次调用的参数。
- 迭代器函数(如
泛型 for 的自动协作:
- 第一次调用:
for会用恒定状态(t)和初始状态(0)调用迭代器,得到i=1, v=a。 - 第二次调用:
for会用恒定状态(t)和上一次返回的 i=1调用迭代器,得到i=2, v=b。 - 直到迭代器返回
nil,循环结束。
- 第一次调用:
为什么叫 “无状态”:迭代器函数(
my_ipairs)本身不保存任何状态(没有闭包变量、全局变量记录i),所有状态(i)都由for循环传递,每次调用都是 “无记忆” 的,完全依赖输入的i来计算下一步。
无状态迭代器的优势:
- 更轻量:不需要闭包来保存状态,减少内存占用。
- 可重入性:同一个迭代器函数可以同时用于多个遍历(因为状态由外部传递,互不干扰)。
Lua 中的 ipairs(遍历数组)和 pairs(遍历表)都是无状态迭代器的典型应用,理解了这个逻辑,就能明白为什么泛型 for 能简洁地遍历各种集合了~
