我们继续来学一些东西
多维数组
数组这东西咱们之前其实已经学过了,基本上没什么说的了,因为lua里面数组字典都是一个东西
但是有一个东西不得不提那就是table的数组的一面的多维数组性
说人话就是和其他语言一样,lua的数组也可以嵌套,而且用法一模一样
二维数组
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
三维数组
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
递归遍历(仅仅数组)
由于只用for循环遍历数组的话,治标不治本,数组每深一层,for循环就要加一层,很麻烦
所以用ipairs迭代器遍历就可以简化
-- 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
使用
--三维数组
local cube = {
--第一层
{
--第二层
{
--第三层
1,
2,
3
},
{
--第三层
4,
5,
6
}
},
{
--第二层
{
--第三层
7,
8,
9
},
{
--第三层
10,
11,
12
}
}
}
walkPrint(cube)
不过这个只适用于数组而非字典
但是对于字典要特别注意,前面也就是lua---基本语法和关键字那一篇我们讲过了字典没有1,2,3,4,...这样的索引,但是有键值对
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的规则是:
先按类型排序:数字 < 字符串 < 布尔 < 其他;
同类型内再比较:
数字按数值大小;
字符串按字典序(逐字节比较);
布尔按
tostring比较,"false" < "true",所以false在前,true在后最后按这个顺序输出
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
使用
我们可以验证一下
local t4 = {
z = {
[3] = "z3",
[1] = "z1",
[2] = "z2",
["b"] = "zb",
["a"] = "za"
},
a = {
[10] = "a10",
[2] = "a2",
["x"] = "ax"
}
}
walkPrint(t4)
得到
["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 迭代器也就是无状态迭代器的一种
local 表名 = {....}
for index, value in ipairs(表名) do
print(index, value)
end
local 表名 = {....}
for index, value in pairs(表名) do
print(index, value)
end
无状态的迭代器(stateless iterator)
无状态:迭代函数本身不保存任何内部状态,它只依赖两个输入:
- 状态(state)常量(通常是固定的数据集合,比如表)
- 控制(control)变量(循环中表示当前位置的索引或计数器)
循环机制:每一次迭代,Lua 都调用迭代函数
f(state, control),由迭代函数返回下一个元素特点 : 第一个返回值既是循环变量,又会成为下一轮的
c;它是nil就停止优势:不需要闭包开销,效率高,适合简单、固定的序列遍历
for var1, var2 in f, s, c do
-- 循环体
end
in 后面有三个东西:
| 名字 | 含义 |
|---|---|
f | 迭代函数,每轮被调用一次 |
s | 状态(state),每次原样传给 f,通常是要遍历的表或上限 |
c | 控制变量(control)的初始值,也就是"从哪开始" |
等价于
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
这里我们可以举两个例子:
例一
-- 无状态迭代器
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 时循环结束
特点:迭代器本身不记状态,状态由循环外部传递进来
例二
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格式
ipairs(t) -- 返回(ipairs内部封装的迭代函数, t, 0)
pairs(t) -- 返回 (next, t, nil)
所以下面两种写法是等价的:
for i, v in ipairs(t) do ... end
for i, v in ipairs内部封装的迭代函数, t, 0 do ... end
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内部封装的迭代函数没有暴露成全局变量,我们无从得知也无法使用
但是逻辑应该类似于
local function ipairsaux(t, i)
i = i + 1
local v = t[i]
if v ~= nil then
return i, v
end
end
有序和无序的区别
ipairs | pairs | |
|---|---|---|
| 遍历范围 | 从索引 1 开始,依次 1, 2, 3… | 表里所有键值对 |
| 顺序 | 保证按索引递增 | 不保证顺序 |
| 何时停止 | 遇到第一个 nil 就停 | 遍历完所有键 |
多状态的迭代器(stateful iterator)
多状态:迭代器需要维护多个状态,可能包括:
- 当前索引
- 集合长度
- 缓存数据
实现方式:
- 闭包:迭代函数内保存状态变量
- 表封装状态:状态信息放在 table 中,迭代函数每次读取
特点:迭代器自己记住状态(用闭包把状态"藏"在迭代函数内部),
for后面只需要写一个函数调用,不用再传 状态变量s和控制变量c
例一
这个也就是我们上面递归遍历键值对的原理之一
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。
特点:迭代器自己保存状态,外部循环无需传递状态。
例二
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, pairs | string.gmatch, io.lines |

