0x01:什么是表
- 表(table)是Lua语言中最主要和强大的数据结构,可以以一种简单、统一且高效的方式表示数组、集合等
- 在Lua中的表实质上是一种关系型数组,这种数组不仅可以使用数值作为索引,也可以使用字符串或其他任意类型(除了
nil
以外)的值作为索引 - 在Lua中的表是不固定大小的,你可以根据自己的需求对表进行扩容,在有新数据插入时表的长度会自动增长
- 在Lua中,表永远都是匿名的,表变量名只是对表地址的引用;当发生拷贝时,Lua不会进行深拷贝,而只是拷贝了表的引用
- Lua通过表来表示模块(module)、包(package)和对象(object)
0x02:表的构造
在Lua中,我们使用构造器表达式来创建表,其最简单的形式就是
{}
data = {} type(data)
- Lua的表要求表中的每一项都是KEY - VALUE的形式
- 在Lua中,使用
data['KEY']
或者data.KEY
我们可以通过KEY值获取表中的VALUE值 - 当构造器表达式中没有KEY只有VALUE时,Lua会把这个表当成一个数组来看待,KEY就是VALUE在数组中的下标(从1开始)
- 但是以上俩种构造方式不能使用特殊的值作为KEY值(比如负索引、特殊符号等)来初始化表元素,为了解决这种需求,我们可以使用一个更加通用的构造器
- 对于一个表而言,当程序中不再有指向它的引用时,Lua将会将其删除,并释放这个表所占用的内存
0x03:遍历表
- 我们可以使用
pairs
迭代器遍历表中的键值对
-- FILE: 11_pairs_table.lua
data = {10, print, x = 12, k = 'fox'}
for k, v in pairs(data) do
print(k, v)
end
受限于Lua底层的实现机制,遍历过程中元素的出现顺序可能是随机的,相同的程序在每次运行时也可能产生不同的顺序。
- 对于列表,我们可以使用
ipairs
迭代器
-- FILE: 12_ipairs_table.lua
data = {10, print, 12, 'fox'}
for k, v in ipairs(data) do
print(k, v)
end
此时,Lua会确保遍历按照顺序进行的
0x04:表标准库
Lua的表标准库提供了一些操作表的常用函数
表连接
table.concat(table, sep, start, end)
,该函数可以在指定table的数组部分从start位置到end位置的所有元素,元素间以指定的分隔符(sep)隔开,并返回连接后的字符串。
data = {'fox', 'cat', 'dog', 'rat'}
table.concat(data)
-- foxcatdograt
table.concat(data, ',')
-- fox,cat,dog,rat
table.concat(data, ',', 1, 3)
-- fox,cat,dog
type(table.concat(data))
-- string
表插入
table.insert(table, pos, value)
,该函数可在table的数组部分指定位置(pos)插入值为value的一个元素;pos参数可选,默认为table的长度,即为表的最后一个元素。
data = {'fox', 'cat', 'dog', 'rat'}
data[#data] -- 取出最末尾的元素
-- rat
table.insert(data, 'bat')
data[#data] -- 取出最末尾的元素
-- bat
data[2] -- 取出第二个元素
table.insert(data, 2, 'tiger') -- 在第二个位置处插入tiger
data[2] -- 取出第二个元素
表删除
table.remove(table, pos)
返回table数组部分位于pos位置的元素,并将其从table中移除,其后的元素会被向前移动;pos参数可选,默认为table的长度,即为表的最后一个元素。
data = {'fox', 'cat', 'dog', 'rat'}
data[#data] -- 取出最末尾的元素
-- rat
table.remove(data) -- 删除表末尾的元素
-- rat
data[#data]
-- dog
data = {'fox', 'cat', 'dog', 'rat'}
data[3]
-- dog
table.remove(data, 3)
-- dog
data[3]
-- rat
表排序
table.sort(table, comp)
可以对给定的table数组部分进行升序排序。comp参数可选,默认情况下是对表作升序排序;可以传入一个自定义比较函数,接收param1、param2,如果param1排在param2前面,则函数返回true,否则返回false。
-- FILE: 13_sort_table.lua
data = {'fox', 'cat', 'dog', 'rat'}
print('-----排序前-----')
for k, v in ipairs(data) do
print(k, v)
end
-- 默认排序
table.sort(data)
print('-----排序后-----')
for k, v in ipairs(data) do
print(k, v)
end
print('---自定义函数---')
-- 自定义升序排序
function asc_sort(p1, p2)
return p1 < p2
end
table.sort(data, asc_sort)
print('---升序排序后---')
for k, v in ipairs(data) do
print(k, v)
end
-- 自定义降序排序
function desc_sort(p1, p2)
return p1 > p2
end
table.sort(data, desc_sort)
print('---降序排序后---')
for k, v in ipairs(data) do
print(k, v)
end
表展开
table.unpack(table, start, end)
该函数将table的数组部分展开,并赋值给对应的变量;参数start与end是可选的,默认start为表的起始位置,end为表的末尾位置。
data = {'fox', 'cat', 'dog', 'rat'}
a, b, c, d = table.unpack(data)
移动表元素
table.move(table, start, end, pos, table2)
该函数可以将table数组部分从start到end的元素拷贝到table2位置pos上;参数table2可选,若为空则是对table自身进行操作。
-- FILE: 14_move_table.lua
data = {'fox', 'tiger', 'cat', 'lion', 'dog', 'rat'}
print('-----移动前-----')
for k, v in ipairs(data) do
print(k, v)
end
table.move(data, 2, 3, #data)
print('-----移动后-----')
for k, v in ipairs(data) do
print(k, v)
end
-- FILE: 15_move_table_2.lua
data1 = {'fox', 'cat', 'dog', 'rat'}
data2 = {'tiger', 'lion'}
print('---移动前data1---')
for k, v in ipairs(data) do
print(k, v)
end
print('---移动前data2---')
for k, v in ipairs(data2) do
print(k, v)
end
table.move(data1, 2, 3, #data1, data2)
print('---移动后data1---')
for k, v in ipairs(data1) do
print(k, v)
end
print('---移动后data2---')
for k, v in ipairs(data2) do
print(k, v)
end
表打包
table.pack(p1, p2, ...)
该函数可以将参数p1,p2,...打包成表,并将其返回。
-- FILE: 16_pack_table.lua
data = table.pack('fox', 'cat', 'dog', 'rat')
print('打包后data的类型为:'..type(data))
for k, v in ipairs(data) do
print(k, v)
end
0x05:安全访问
如果我们想确认在指定的库中是否存在某个函数,而这个库确实存在,则可以直接使用以下表达式
if lib.foo then ...
但是,如果我们不确定这个库是否存在,则在这之前还需要再套一层表达式
if lib and lib.foo then ...
当然,现在看起来并不是很复杂,但是如果它嵌套的深度特别深时,则会让我们的表达式变得特别复杂,而且特别容易出错,如下所示
if lib and lib.foo and lib.foo.data and lib.foo.data.animal and lib.foo.data.animal.fox and ......
若是中途敲错一个,则全部都错。
正确姿势
Lua语言并没有提供安全访问操作符,但是我们可以使用其他语句在Lua中模拟安全访问操作符
形如上述的情况,我们可以这样重写
fox_data = ((((lib or {}).foo).data or {}).animal or {}).fox
或者可以这样写
E = {}
fox_data = ((((lib or E).foo).data or E).animal or E).fox
这样重写后,只要上述的成员访问过程出现不存在的情况时,Lua不会直接抛出异常,而是返回nil
0x06:参考文献
- 《Lua程序设计(第4版)》
- 乐逍遥Jun --- Lua基础之table详解
图都裂了