循环背后的魔法:Lua 迭代器深度解析

一、泛型 for 循环的解构

要理解迭代器,我们首先必须拆解泛型 for 循环的语法: for var_1, var_2, ..., var_n in <表达式列表> do ... end

这里的关键是 <表达式列表>。当循环开始时,Lua 会对这个列表求值一次,并期望它返回三个值

  1. 迭代器函数(Iterator Function) :一个函数,for 循环在每次迭代时都会调用它来获取下一个(或下一组)值。
  2. 不变的状态(Invariant State):一个值(通常是表),它会在整个循环过程中被传入迭代器函数,用于维持状态。它本身通常不改变。
  3. 初始控制变量(Initial Control Variable):第一个要传入迭代器函数的值。

实际上,一个泛型 for 循环:

lua 复制代码
for var_1, var_2 in explist do
    -- 循环体
end

在 Lua 内部,等价于下面这段 while 循环代码:

lua 复制代码
-- 1. 求值,获取三个关键部分
local _iterator, _state, _control_var = explist

-- 2. 循环开始
while true do
    -- 3. 调用迭代器函数获取新值
    local var_1, var_2 = _iterator(_state, _control_var)
  
    -- 4. 更新控制变量为第一个返回值
    _control_var = var_1

    -- 5. 如果第一个返回值为 nil,则循环结束
    if var_1 == nil then
        break
    end

    -- 6. 执行循环体
    -- 循环体
end

这就是 for 循环的全部秘密!它只是一种 while 循环的语法糖,遵循着这个"三值协议"。

二、迭代器的两种核心模式

理解了协议后,我们来看看两种最主要的迭代器实现模式。

1. 无状态迭代器(Stateless Iterator)

"无状态"指的是不变的状态_state),它通常就是我们正在遍历的那个对象(例如一个表)。下一次迭代所需的一切信息都包含在控制变量 (_control_var)中。

Lua 内置的 ipairspairs 就是典型的无状态迭代器。pairs(t) 实际上等价于 return next, t, nil

  • 迭代器函数next,Lua 内置的用于遍历表的函数。
  • 不变的状态t,我们正在遍历的表。
  • 初始控制变量nil,告诉 next 函数从头开始。

让我们亲手实现一个 ipairs 来加深理解:

lua 复制代码
local function my_ipairs_iterator(tbl, index)
    index = index + 1
    local value = tbl[index]
    if value then
        return index, value
    end
end

function my_ipairs(tbl)
    -- 返回三元组:迭代器函数, 状态, 初始控制变量
    return my_ipairs_iterator, tbl, 0
end

-- 使用我们自己的 ipairs
local days = { "Monday", "Tuesday", "Wednesday" }
for index, day in my_ipairs(days) do
    print(index, day)
end
-- 输出:
-- 1   Monday
-- 2   Tuesday
-- 3   Wednesday

你看,my_ipairs 完美地遵循了三值协议,for 循环也因此能正确地与它协作。

2. 有状态迭代器(Stateful Iterator)

"有状态"指的是遍历的对象无法作为一个不变的状态_state),需要我们添加状态构建一个新的不变的状态_state

lua 复制代码
-- 这是我们的迭代器函数。
-- 它接收不变的状态表和当前的控制变量。
local function range_iterator(state, current_val)
    -- 1. 检查当前值是否已经超出限制
    if current_val >= state.limit then
        return nil -- 返回 nil 来结束循环
    end

    -- 2. 计算下一个值
    local next_val = current_val + state.step

    -- 3. 返回下一个值,它将成为下一次循环的控制变量
    return next_val
end

-- 这是我们的迭代器构造函数
function range(start, finish, step)
    -- 设置默认值
    start = start or 1
    finish = finish or 10
    step = step or 1

    -- 创建包含不变信息的状态表
    local state = {
        limit = finish,
        step = step
    }

    -- **关键**:返回迭代器三元组
    -- 1. 迭代器函数: range_iterator
    -- 2. 状态表: state
    -- 3. 初始控制变量: 从 start 开始,所以初始值为 start
    --   (但为了让第一个返回的值就是 start 本身,
    --    初始控制变量需要是 start - step,这样第一次相加后正好是 start)
    return range_iterator, state, start - step
end

print("从 1 到 5,步长为 1:")
for i in range(1, 5) do
    print(i)
end
-- 输出:
-- 1
-- 2
-- 3
-- 4
-- 5

在lua中,常用闭包 (Closure)来实现状态的保存,所以有状态迭代器的实现通常使用闭包 实现,闭包实现过程中,没有显式地利用 for 循环传递的 statecontrol_var 参数

lua 复制代码
function close_iterator(start, finish, step)
    start = start - 1
    finish = finish or 10
    step = step or 1
    local current = start -- 保存的状态
    return function ()
        current = current + step
        if current <= finish then
            return current
        else
            return nil
        end
    end
end

for i in close_iterator(1, 5, 2) do
    print(i)
end

结语

点个赞,关注我获取更多实用 Lua 技术干货!如果觉得有用,记得收藏本文!

相关推荐
元拓数智3 小时前
现代前端状态管理深度剖析:从单一数据源到分布式状态
前端·1024程序员节
mapbar_front3 小时前
Electron 应用自动更新方案:electron-updater 完整指南
前端·javascript·electron
天一生水water3 小时前
three.js加载三维GLB文件,查看三维模型
前端·1024程序员节
无风听海3 小时前
HarmonyOS之启动应用内的UIAbility组件
前端·华为·harmonyos
冰夏之夜影4 小时前
【科普】Edge出问题后如何恢复出厂设置
前端·edge
葱头的故事4 小时前
vant van-uploader上传file文件;回显时使用imageId拼接路径
前端·1024程序员节
Mintopia5 小时前
🇨🇳 Next.js 在国内场景下的使用分析与实践指南
前端·后端·全栈
Mintopia5 小时前
深度伪造检测技术在 WebAIGC 场景中的应用现状
前端·javascript·aigc
BUG_Jia5 小时前
如何用 HTML 生成 PC 端软件
前端·javascript·html·桌面应用·1024程序员节