表(Table)——Lua 的灵魂

9.1 什么是 Table

在 Lua 中,table 是唯一的数据结构容器。它同时扮演着其他语言中数组字典集合对象模块等多重角色。

“Table is the sole data structuring mechanism in Lua.”

核心特性

1
2
3
4
5
6
7
8
9
10
11
12
-- table 是关联数组:任何值(除 nil)都可以作为 key
local t = {}
t[1] = "one" -- 整数 key
t["name"] = "Lua" -- 字符串 key
t[true] = "真值" -- 布尔 key
t[print] = "函数 key" -- 函数 key(!)

-- table 是引用类型
local a = {1, 2, 3}
local b = a
b[1] = 100
print(a[1]) --> 100(a 和 b 指向同一个表)

9.2 表的创建与构造器

空表

1
local t = {}

数组风格的构造器(列表)

1
2
3
4
5
6
7
-- 自动使用连续整数作为 key,从 1 开始
local colors = {"red", "green", "blue"}
print(colors[1]) --> red(注意:Lua 索引从 1 开始)
print(colors[3]) --> blue

-- 等价于:
local colors = {[1] = "red", [2] = "green", [3] = "blue"}

字典风格的构造器(记录)

1
2
3
4
5
6
7
8
9
local person = {
name = "张三",
age = 25,
city = "北京"
}

print(person.name) --> 张三(语法糖,等价于 person["name"])
print(person["city"]) --> 北京
print(person.age) --> 25

混合构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
local data = {
"apple", -- [1]
"banana", -- [2]
"cherry", -- [3]

name = "水果列表",
count = 3,

[10] = "extra", -- 显式指定 key
["flag"] = true
}

print(data[1]) --> apple
print(data.name) --> 水果列表
print(data[10]) --> extra

不可用作 key 的值

1
2
3
4
5
6
7
8
9
10
11
12
13
-- nil 不能用作 key(会直接删除该条目)
-- 浮点数可以用作 key,但最好避免
t = {}
t[nil] = "error" -- 错误!这条赋值会被忽略

-- 表的 key 可以是任何值(除了 nil)
local key = {}
t[key] = "value"
print(t[key]) --> value

-- 注意:不同的表是不同的 key
local key2 = {}
print(t[key2]) --> nil(key2 是另一个表)

9.3 索引与赋值

两种访问方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
local t = {name = "Lua", version = 5.4}

-- 点号语法(适合字符串字面量 key)
print(t.name) --> Lua
t.version = 5.5

-- 方括号语法(适合动态 key 或非字符串 key)
local key = "name"
print(t[key]) --> Lua(动态 key)

local k = 1
local arr = {10, 20}
print(arr[k]) --> 10(数值 key)

-- 点号不支持的内容(必须用方括号)
-- t.my-key --> 错误(- 不是合法标识符)
-- t["my-key"] --> 正确

新增与修改

1
2
3
4
5
6
7
8
9
local t = {}

-- 新增条目
t.name = "Lua"
t["version"] = 5.4
t[1] = "first"

-- 修改已存在的条目
t.name = "Lua 5.4"

删除条目

将值设为 nil 即可删除:

1
2
3
4
5
6
local t = {a = 1, b = 2, c = 3}

print(t.b) --> 2

t.b = nil -- 删除键 "b"
print(t.b) --> nil

9.4 数组部分——约定从 1 开始

Lua 的数组索引约定从 1 开始,这是 Lua 的传统设计:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- 创建一个数组
local arr = {100, 200, 300, 400, 500}
print(#arr) --> 5(长度)

-- 遍历
for i = 1, #arr do
print(i, arr[i])
end

-- 多维数组(表套表)
local matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
}

print(matrix[2][3]) --> 6(第 2 行第 3 列)

数组空洞问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- 如果数组中间有 nil,会产生"空洞"
local arr = {1, nil, 3, nil, 5}

-- # 对带空洞的数组行为未定义
print(#arr) --> 可能为 5,也可能为 1
-- 实际结果取决于内部实现,不要依赖!

-- 安全的做法:用 table.maxn(Lua 5.2 后移除)
-- 或自己遍历
local function maxn(t)
local max = 0
for k in pairs(t) do
if type(k) == "number" and k > max then
max = k
end
end
return max
end

print(maxn(arr)) --> 5

9.5 遍历 Table

pairs():遍历所有键值对

1
2
3
4
5
6
7
8
9
10
11
local t = {a = 1, b = 2, c = 3, [1] = "one", [2] = "two"}

for k, v in pairs(t) do
print(k, v)
end
-- 输出顺序不确定(哈希表特性):
-- 1 one
-- 2 two
-- a 1
-- b 2
-- c 3

ipairs():遍历连续整数索引

1
2
3
4
5
6
7
8
9
local t = {"a", "b", "c", key = "value", [5] = "e"}

for i, v in ipairs(t) do
print(i, v)
end
-- 输出:1 a 2 b 3 c(遇到 key 和 5 就停了)
-- ipairs 从 1 开始递增,直到遇到 nil 退出

print(t[5]) --> e(值存在,但 ipairs 不会遍历到)

pairs vs ipairs 对比

特性 pairs ipairs
遍历范围 所有 key 仅连续整数 key(从 1 开始)
遍历顺序 无序 按索引递增
遇到 nil 继续遍历 停止
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 通用遍历策略
local t = {10, 20, 30}

-- 数组部分用 ipairs
for i, v in ipairs(t) do
print("索引", i, "值", v)
end

-- 字典部分用 pairs
local dict = {a = 1, b = 2, c = 3}
for k, v in pairs(dict) do
print("键", k, "值", v)
end

-- 混合表用 pairs(全面但无序)

9.6 表的实用操作

判断表是否为空

1
2
3
4
5
6
local function is_empty(t)
return next(t) == nil
end

print(is_empty({})) --> true
print(is_empty({1})) --> false

表合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
local function merge(t1, t2)
local result = {}
for k, v in pairs(t1) do
result[k] = v
end
for k, v in pairs(t2) do
result[k] = v
end
return result
end

local a = {a = 1, b = 2}
local b = {b = 3, c = 4}
local c = merge(a, b)
-- c = {a = 1, b = 3, c = 4}(b 被后面的覆盖)

表的切片

1
2
3
4
5
6
7
8
9
10
11
local function slice(arr, start, finish)
local result = {}
for i = start, finish do
table.insert(result, arr[i])
end
return result
end

local fruits = {"苹果", "香蕉", "橘子", "葡萄", "西瓜"}
local some = slice(fruits, 2, 4)
-- some = {"香蕉", "橘子", "葡萄"}

9.7 表与性能

预分配大小

1
2
3
4
5
6
7
8
-- 预先填充 nil 可以提升后续插入性能
local t = {}
for i = 1, 1000 do
t[i] = 0 -- 直接赋值,比 insert 快
end

-- 或者在构造时就指定初始化大小(Lua 5.3+)
-- 实际上 Lua 会自动调整 table 大小

避免使用 nil 在数组中间

1
2
3
4
5
6
-- 不好的做法
local t = {1, nil, 3, nil, 5}

-- 好的做法(用标记值代替 nil)
local ABSENT = {}
local t = {1, ABSENT, 3, ABSENT, 5}

使用 table.insertt[#t+1]

1
2
3
4
5
6
7
-- 在末尾添加元素
local t = {}
for i = 1, 100 do
-- 这两种方式等价
table.insert(t, i) -- 方式一:函数调用
-- t[#t + 1] = i -- 方式二:直接赋值(稍快)
end

9.8 本章小结

  • table 是 Lua 唯一的数据结构,同时是数组、字典、集合
  • 构造器 {} 创建表,支持列表、字典、混合风格
  • 点号 t.key 和方括号 t["key"] 两种索引方式
  • 赋值 nil 删除条目
  • 数组索引从 1 开始
  • pairs() 遍历全部,ipairs() 遍历连续整数索引
  • 避免数组空洞

练习题

  1. 创建一个 “学生信息管理系统” 的表:包含多个学生,每个学生有姓名、年龄、分数
  2. 用两种方法(for 循环和 ipairs)遍历一个数组并求和
  3. 写一个函数 table_to_string(t),将任意表转换为可读的字符串表示
  4. 实现 table.equals(t1, t2) 比较两个表的内容是否相同(深层比较)
  5. 用 table 实现一个集合:支持 addremovecontainsunionintersection
ByteFisher
分享编程技术 · 记录钓鱼乐趣
扫码关注
▸ 扫码关注 ◂
Lua基础教程 系列
第 9/15 篇
分享: