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.insert
和table.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)