04-表

争取两三周入门Lua!

表(Table)是Lua中唯一的强大的数据结构。使用表,Lua可以以一种简单、同一且高效的方式来表示数组、集合、记录和其他很多数据结构。Lua语言也使用表来表示包(Package)和其他对象。例如,当调用math.sin()时,实际上Lua以字符串sin为键,检索表math。

构造

空构造

使用空构造器创建表:

a = {}			-- 创建一个表然后用表的引用赋值
a["x"] = 10		-- 加入新元素, key = "x", val = 10
a[20] = "Great" -- 加入新元素, key = 20, val = "Great"
a["x"]			--> 10

列表式构造

列表式(list-style)构造:

days = {"Sunday", "Monday"}

记录式构造

记录式(record-style)构造:

a = {x = 10, y = 20}
a["x"]					--> 10

当然,二者也能混用/嵌套使用:

line = {
    color = "blue", thickness = 2, npoints = 2,
    {x = 0, y = 0},
    {x = -10, y = 0}
}

但这两种构造器都有各自的局限。例如,使用这两种构造器时,不能使用负数索引,因为都是从1初始化的。

更通用的构造

可通过方括号括起来的表达式显式指定每一个索引:

opnames = {["+"] = "add", ["-"] = "sub", ["*"] = "mul", ["/"] = "div"}

索引

同个表中存储的值可有不同类型的索引,并且可按需增长以容纳新的元素:

a = {}
-- 创建1000个新元素
for i = 1, 1000	do a[i] = i * 2 end
-- 删除元素
a[2] = nil

注意最后一行,如同全局变量,未经初始化的表元素也为nil,将nil赋值给表元素可将其删除。因为全局变量实际上也是用表存储的。

作为结构体的表

当把表作为结构体使用时,可把索引当做成员名称使用:

a = {}
a.x = 10		-- 等价于a["x"] = 10
a.x				--> 10
a.y				--> nil

混淆现象

由于能使用任意类型索引表,所以在索引表时可能会有混淆现象:

i = 10;	j = "10"; k = "+10"
a = {}
a[i] = "number"
a[j] = "String"
a[k] = "another string"
a[i]						--> number
a[j]						--> string
a[k]						--> another string
a[tonumber(i)]				--> number
a[tonumber(j)]				--> number

发现如果强行将i,j转换为数值类型,那么a[j]实际上为a[i],发生混淆。

而使用数值类型(整型和浮点型)作为索引则不会发生混淆,当被用作索引时,任何能够被转换为整型的浮点型都会被转换为整型:

a = {}
a[2.0] = 10
a[2.1] = 20
a[2]		--> 10
a[2.0]		--> 10
a[2.1]		--> 20

表示数组、列表和序列

数组、列表

如果想表示常见的数组或列表,那么只需要使用整型作为索引的表即可。同时,也不需要预先声明表的大小,只需要直接初始化需要的元素即可:

-- 读取10行, 然后保存在一个表中
a = {}
for i = 1, 10 do
    a[i] = io.read()
end

在Lua语言中,数组索引按照惯例是从1开始的,而不是从0开始。数组/列表的长度可存放在常量中,不过通常 存储在键”n”所对应的值中

序列

没有nil元素的数组被称为序列,这样当我们碰到nil时说明序列结束了,那么前一个索引就是该序列的长度。

可用#操作符来获取序列的长度:

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

-- 输出序列a的最后一个值
print(a[#a])

-- 删除序列a最后一个值
a[#a] = nil

-- 把 'v' 添加到序列最后
a[#a + 1] = v

遍历

使用pairs迭代器

可以用pairs迭代器遍历表中的键值对:

t = {10, print, x = 12, k = "hi"}
for k, v in pairs(t) do
    print(k, v)
end
--[[
1       10
2       function: 0000000065b9cff0
k       hi
x       12
]]

受限于表在Lua语言中的底层实现机制,遍历过程中元素的出现顺序可能是随机的。

使用ipairs迭代器

对于列表而言,可用ipairs迭代器:

t = {10, print, 12, "hi"}
for k, v in ipairs(t) do
    print(k, v)
end
--[[
1       10
2       function: 0000000065b9cff0
3       12
4       hi
]]

使用ipairs迭代器遍历的元素出现顺序是固定的。

使用for循环

另外一种遍历序列的方式是使用数值型for循环:

for k = 1, #t do
    print(t[k]) 
end
--[[
10
function: 0000000065b9cff0
12
hi
]]

嵌套访问问题

假设要确定某库中是否含有某函数,可这样写:

zip = com and com.dir and com.dir.addr and com.dir.addr.zipcode

可以发现一共对表com访问了6次,而不是3次,且太长了有可能写错。可以这样写:

zip = (((com or {}).dir or {}).addr or {}).zipcode

这样就很高效了,只访问了3次。

表标准库

表标准库提供了操作列表和序列的一些常用函数。

table.insert(table, pos, val)

向序列的指定位置插入一个元素,其他元素依次后移。例如,t = {10, 20, 30},调用table.insert(t, 1, 15)后,会变成t = {15, 10, 20, 30}。如果不指定位置,将会在序列最后插入元素。

table.remove(table, pos)

删除并返回序列指定位置的元素,然后将其后的元素向前移动补缺。如果不指定位置,则会删除序列最后一个元素。

合理使用table.inserttable.remove这两个函数,可以实现栈,队列,双端队列。

table.move(a, f, e, t)

Lua5.3中对于移动表中的元素提供了一个更通用的函数,调用该函数可以将表a中索引[f, e]的元素移动到位置t上。

-- 在列表a开头插入元素
table.move(a, 1, #a, 2)
a[1] = 123

该函数还有一个可选参数,用于将a的结果拷贝到目标列表中:

-- 返回列表a的一个副本
table.move(a, 1, #a, 1, {})
-- 将列表a中所有元素复制到列表b末尾
table.move(a, 1, #a, #b + 1, b)