我们继续来学一些东西

多维数组

数组这东西咱们之前其实已经学过了,基本上没什么说的了,因为lua里面数组字典都是一个东西

但是有一个东西不得不提那就是table的数组的一面的多维数组性

说人话就是和其他语言一样,lua的数组也可以嵌套,而且用法一模一样

二维数组

lua
12345678910111213141516
local matrix = {
    { 1, 2, 3 },
    { 4, 5, 6 },
    { 7, 8, 9 }
}

print(matrix[1][1]) -- 1
print(matrix[2][3]) -- 6
print(matrix[3][2]) -- 8

for i = 1, #matrix do
    for j = 1, #matrix[i] do
        io.write(matrix[i][j], " ")
    end
    print()
end

​ image-20260310180344743

三维数组

lua
1234567891011121314151617181920212223
local cube = {
    {
        { 1, 2 },
        { 3, 4 }
    },
    {
        { 5, 6 },
        { 7, 8 }
    }
}

for i = 1, #cube do
    print("layer", i)

    for j = 1, #cube[i] do
        for k = 1, #cube[i][j] do
            io.write(cube[i][j][k], " ")
        end
        print()
    end

    print()
end

​ image-20260310180446020

递归遍历(仅仅数组)

由于只用for循环遍历数组的话,治标不治本,数组每深一层,for循环就要加一层,很麻烦

所以用ipairs迭代器遍历就可以简化

lua
1234567891011121314151617181920212223242526272829303132333435
-- t        当前遍历的数组
-- callback 处理叶子数据的函数
-- path     记录当前路径
local function walkArray(t, callback, path)
    path = path or {}

    -- 只遍历数组:从 1 开始,遇到 nil 停止
    for i, v in ipairs(t) do
        path[#path + 1] = i

        if type(v) == "table" then
            -- 还是数组,继续往里递归
            walkArray(v, callback, path)
        else
            -- 到达叶子,执行回调
            callback(v, path)
        end

        -- 回溯:删除最后一个路径
        path[#path] = nil
    end
end


local function walkPrint(t)
    walkArray(t, function(value, path)
        io.write("index ")

        for i = 1, #path do
            io.write("[" .. path[i] .. "]")
        end

        print(" = " .. tostring(value))
    end)
en

使用

lua
1234567891011121314151617181920212223242526272829303132333435363738
--三维数组
local cube = {

    --第一层
    {
        --第二层
        {
            --第三层
            1,
            2,
            3
        },
        {
            --第三层
            4,
            5,
            6
        }
    },

    {
        --第二层
        {
            --第三层
            7,
            8,
            9
        },
        {
            --第三层
            10,
            11,
            12
        }
    }
}

walkPrint(cube)

不过这个只适用于数组而非字典

但是对于字典要特别注意,前面也就是lua---基本语法和关键字那一篇我们讲过了字典没有1,2,3,4,...这样的索引,但是有键值对

lua
12345678910
local arr = { "a", "b", "c" }
-- 等价于
arr[1] = "a"
arr[2] = "b"
arr[3] = "c"

local dict = { name = "Lua", version = 5.4 }
-- 等价于
dict["name"] = "Lua"
dict["version"] = 5.4

然后就是lua---循环和条件语句这一篇我们知道了ipairs循环的特性:

遇到 nil(即遇到空值断层) 后会停止遍历,而且遍历时遇到键值对会自动跳过

以及for循环只能用于数组,所以我们只能用pairs无序遍历字典(这算lua的缺陷还是优点?)

递归遍历(适用于数组和字典)

由于我们已知pairs是无序的,所以我们需要做的操作是给定规则让其看似有序

例如我们这里的规则是

先用 pairs 把字典的键全部收进一个数组(此时顺序不保证),defaultSort 的规则是:

  1. 先按类型排序:数字 < 字符串 < 布尔 < 其他;

  2. 同类型内再比较:

    • 数字按数值大小;

    • 字符串按字典序(逐字节比较);

    • 布尔按 tostring 比较,"false" < "true",所以 false 在前,true 在后

  3. 最后按这个顺序输出

lua
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
local typeRank = { number = 1, string = 2, boolean = 3 }

local function defaultSort(a, b)
    local ta, tb = type(a), type(b)

    if ta == tb then
        if ta == "number" or ta == "string" then
            return a < b
        end
        return tostring(a) < tostring(b)
    end

    local ra = typeRank[ta] or 4
    local rb = typeRank[tb] or 4

    if ra ~= rb then
        return ra < rb
    end

    return tostring(a) < tostring(b)
end

local function copyPath(path)
    local p = {}
    for i = 1, #path do
        p[i] = path[i]
    end
    return p
end

local function formatPath(path)
    local parts = {}
    for i = 1, #path do
        local k = path[i]
        if type(k) == "string" then
            parts[i] = '["' .. k .. '"]'
        else
            parts[i] = "[" .. tostring(k) .. "]"
        end
    end
    return table.concat(parts)
end

local function walkArray(t, callback, options)
    options            = options or {}

    local order        = options.order ~= false
    local includeEmpty = options.includeEmpty or false
    local detectCycle  = options.detectCycle ~= false
    local onCycle      = options.onCycle
    local sort         = options.sort or defaultSort

    local visiting     = {}

    local function keysOf(tbl)
        local keys = {}
        for k in pairs(tbl) do
            keys[#keys + 1] = k
        end

        if order then
            table.sort(keys, sort)
        end

        return keys
    end

    local function walk(cur, path)
        if type(cur) ~= "table" then
            callback(cur, copyPath(path), formatPath(path))
            return
        end

        if detectCycle and visiting[cur] then
            if onCycle then
                onCycle(cur, copyPath(path), formatPath(path))
            end
            return
        end

        visiting[cur] = true

        local keys = keysOf(cur)

        if #keys == 0 and includeEmpty then
            callback(cur, copyPath(path), formatPath(path))
        end

        for _, k in ipairs(keys) do
            local v = cur[k]
            path[#path + 1] = k

            if type(v) == "table" then
                walk(v, path)
            else
                callback(v, copyPath(path), formatPath(path))
            end

            path[#path] = nil
        end

        visiting[cur] = nil
    end

    walk(t, {})
end


local function walkPrint(t, options)
    walkArray(t, function(value, path, pathStr)
        if pathStr == "" then
            print(tostring(value))
        else
            print(pathStr .. " = " .. tostring(value))
        end
    end, options)
end

使用

我们可以验证一下

lua
123456789101112131415
local t4 = {
    z = {
        [3] = "z3",
        [1] = "z1",
        [2] = "z2",
        ["b"] = "zb",
        ["a"] = "za"
    },
    a = {
        [10] = "a10",
        [2]  = "a2",
        ["x"] = "ax"
    }
}
walkPrint(t4)

得到

lua
12345678
["a"][2] = a2
["a"][10] = a10
["a"]["x"] = ax
["z"][1] = z1
["z"][2] = z2
["z"][3] = z3
["z"]["a"] = za
["z"]["b"] = zb

迭代器

迭代器就是一个"每次调用都给你下一个值"的函数。 for 循环负责反复调用它,直到它返回 nil

迭代器包括无状态的迭代器,多状态的迭代器

其中前面说的遍历表的ipairs和pairs这些东西即泛型 for 迭代器也就是无状态迭代器的一种

lua
12345
local 表名 = {....}

for index, value in ipairs(表名) do
    print(index, value)
end
lua
12345
local 表名 = {....}

for index, value in pairs(表名) do
    print(index, value)
end

无状态的迭代器(stateless iterator)

无状态:迭代函数本身不保存任何内部状态,它只依赖两个输入:

  1. 状态(state)常量(通常是固定的数据集合,比如表)
  2. 控制(control)变量(循环中表示当前位置的索引或计数器)

循环机制:每一次迭代,Lua 都调用迭代函数 f(state, control),由迭代函数返回下一个元素

特点 : 第一个返回值既是循环变量,又会成为下一轮的 c;它是 nil 就停止

优势:不需要闭包开销,效率高,适合简单、固定的序列遍历

lua
123
for var1, var2 in f, s, c do
    -- 循环体
end

in 后面有三个东西:

名字含义
f迭代函数,每轮被调用一次
s状态(state),每次原样传给 f,通常是要遍历的表或上限
c控制变量(control)的初始值,也就是"从哪开始"

等价于

lua
1234567
local f, s, c = ...            -- 先算出这三个值
while true do
    local var1, var2 = f(s, c) -- 每轮调用 f(s, c)
    if var1 == nil then break end -- 第一个返回值是 nil,循环结束
    c = var1                   -- 第一个返回值成为下一轮的 c
    -- 循环体
end

这里我们可以举两个例子:

例一

lua
1234567891011121314151617181920
-- 无状态迭代器
local function iter(n, i)   -- n 是 s,i 是 c
    i = i + 1
    if i <= n then
        return i            -- 返回值成为下一轮的 i
    end                     -- 超过 n 就什么都不返回(即 nil),循环结束
end

for i in iter, 5, 0 do      -- f=iter, s=5, c=0
    print(i)                -- 1 2 3 4 5
end

解析:
iter 是迭代器函数,每次被调用时接收两个参数:
	n(state)
	i(上一次返回的值,初始是 0)

返回下一个值,直到返回 nil 时循环结束

特点:迭代器本身不记状态,状态由循环外部传递进来

例二

lua
12345678910111213141516171819
local function square(max, cur)
    if cur < max then
        cur = cur + 1
        return cur, cur * cur   -- 第一个值是新的 cur,第二个值是平方
    end
end

for i, sq in square, 3, 0 do
    print(i, sq)   -- 1 1 / 2 4 / 3 9
end

解析:

迭代函数:square(max, cur)
每次被调用,传入状态常量 max(最大迭代次数)和控制变量 cur(上一次返回的值)

返回值:返回下一个索引和它的平方 square

Lua 的 for 循环会不断调用 square,直到返回 nil 停止

ipairs(t) 和 pairs(t)

我们可以剖析一下这两种排序函数,可以看到返回值就是我上述例子的f ,s,c格式

lua
12
ipairs(t)  -- 返回(ipairs内部封装的迭代函数, t, 0)
pairs(t)   -- 返回 (next, t, nil)

所以下面两种写法是等价的:

lua
12
for i, v in ipairs(t) do ... end
for i, v in ipairs内部封装的迭代函数, t, 0 do ... end
lua
12
for k, v in pairs(t) do ... end
for k, v in next, t, nil do ... end

next 就是无状态迭代函数:它不记任何东西,每次靠传入的 (t, 上一个key) 找下一个键值对

函数返回什么说明
next(t, k)k2, v2遍历所有键,顺序不保证。k 为 nil 时从头开始

至于ipairs内部封装的迭代函数没有暴露成全局变量,我们无从得知也无法使用

但是逻辑应该类似于

lua
1234567
local function ipairsaux(t, i)
  i = i + 1
  local v = t[i]
  if v ~= nil then
    return i, v
  end
end

有序和无序的区别

ipairspairs
遍历范围从索引 1 开始,依次 1, 2, 3…表里所有键值对
顺序保证按索引递增不保证顺序
何时停止遇到第一个 nil 就停遍历完所有键

多状态的迭代器(stateful iterator)

多状态:迭代器需要维护多个状态,可能包括:

  • 当前索引
  • 集合长度
  • 缓存数据

实现方式:

  1. 闭包:迭代函数内保存状态变量
  2. 表封装状态:状态信息放在 table 中,迭代函数每次读取

特点:迭代器自己记住状态(用闭包把状态"藏"在迭代函数内部), for 后面只需要写一个函数调用,不用再传 状态变量s 和控制变量 c

例一

这个也就是我们上面递归遍历键值对的原理之一

lua
123456789101112131415161718192021222324252627
local function next_pair(t)
    local key = nil
    return function()
        key = next(t, key)
        if key then
            return key, t[key]
        end
    end
end

local t = {a=1, b=2, c=3}
for k, v in next_pair(t) do
    print(k, v)
end
--a       1
--b       2
--c       3

解析:

next_pair(t) 返回一个闭包函数,这个闭包保存了内部状态(key)。

每次调用闭包,都会从表中取下一个键值对。

循环结束条件是闭包返回 nil。

特点:迭代器自己保存状态,外部循环无需传递状态。

例二

lua
12345678910111213141516171819
local array = {"Google", "Runoob"}

function elementIterator(collection)
    local index = 0     -- 这些是闭包记住的状态
    local count = #collection
    -- 返回的这个函数就是迭代函数
    return function()
        index = index + 1
        if index <= count then
            return collection[index]
        end
    end
end

for element in elementIterator(array) do
    print(element)
end
--Google
--Runoob
无状态多状态(闭包)
状态存在哪由 for 传参 (s, c)闭包内部的局部变量
in 后面写法f, s, c一个函数调用
开销小每次遍历建一个闭包
适用简单、位置能用一个值表示的遍历状态复杂或需要缓存
例子ipairs, pairsstring.gmatch, io.lines