目录
注:本系列为《Lua程序设计-第4版》 的读书笔记,其中的见解有不完善的地方,可以在评论区指出,原版请看图书
二. 表永远是匿名的,表本身和保存表的变量之间没有固定关系:
3.1 当把表当作结构体使用时,可以把索引当作成员名称使用(a.name 等价于 a["name"])
3.2 初学者常常会混淆a.x 和 a[x]。实际上,a.x代表的是a["x"],即由字符串“x”索引的表;而a[x]则是指由变量x对应的值索引的表
3.3 数字 1 、字符串“+1”、“01”、和“1”指向的是不同的元素,当不清楚表索引的真实数据类型是,可以使用显示类型转换
4.3 无论使用哪种方式创建表,都可以随时增加或删除表元素:
九. 借助 table.insert 和 table.remove 实现 栈(Stack)、队列(Queue)、和双端队列(Double queue)(会在 编程实操-数据结构中详解)
注:本系列为《Lua程序设计-第4版》 的读书笔记,其中的见解有不完善的地方,可以在评论区指出,原版请看图书
表(Table)是Lua语言中最主要(事实上也是唯一的)和强大的数据结构。使用表,Lua语言可以以一种简单、统一且高效的方式表示数组、集合、记录和其他很多数据结构。
Lua语言也使用表来表示包(package)和其他对象。当调用函数math.sin时,我们可能认为是“调用了math库中函数sin”;而对于Lua语言来说,其实际含义是”以字符串“sin”为键检索表math“。
Lua语言中的表本质上是一种辅助数组,这种数组不仅可以使用数值作为索引,也可以使用字符串或其他任意类型的值作为索引(nil 除外)
表是一种动态分配的对象,程序只能操作指向表的引用(或指针)。除此之外,Lua语言不会进行隐藏的拷贝或创建新的表。
一. 创建表,其最简单的形式是:{}
a = {} --创建一个表然后用表的引用赋值
k = "x"
a[k] = 10 -- 新元素,键是"x",值是10
a[20] = "great" --新元素,键是20,值是"great"
a["x"] --> 10
k = 20
a[k] --> "great"
a["x"] = a["x"] + 1 -- 增加元素"x"的值
a["x"] --> 11
二. 表永远是匿名的,表本身和保存表的变量之间没有固定关系:
a = {}
a["X"] = 10
b = a --b 和 a 引用同一张表
b["x"] --> 10
b["x"] = 20
a["x"] --> 20
a = nil -- 只有b 仍然指向表
b = nil -- 没有指向表的引用了
对于一个表而言,当程序中不再有指向它的引用时,垃圾收集器会最终删除这个表并重用其占用的内存。
三. 表索引
同一个表中存储的值可以具有不同的类型索引,并可以按需增长以容纳新的元素:
a = {} --空表
-- 创建1000个新元素
for i = 1, 1000 do a[i] = i*2 end
a[9] --> 18
a["x"] = 10
a["x"] --> 10
a["y"] --> nil
注意上述代码中的最后一行:如同全局变量一样,未经初始化的表元素为nil,将nil 赋值给表元素可以将其删除。
3.1 当把表当作结构体使用时,可以把索引当作成员名称使用(a.name 等价于 a["name"])
a = {} -- 空白表
a.x = 10 -- 等价于a["x"] = 10
a.x --> 10 -- 等价于a["x"]
a.y --> nil -- 等价于a["y"]
虽然这两种方法是等价且可以自由混用的;不过对于阅读程序的人而言,这两种形式可能代表了不同的意图。(为了提高阅读性,请合理运用)
Ⅰ. a.name 的点分形式清晰地说明了表是被当作结构体使用的,此时表实际上是由固定的、预定义的键组成的集合
Ⅱ. a["name"]的字符串索引形式则说明了表可以使用任意字符串为键,并且出于某种原因我们操作的是指定的键。
3.2 初学者常常会混淆a.x 和 a[x]。实际上,a.x代表的是a["x"],即由字符串“x”索引的表;而a[x]则是指由变量x对应的值索引的表
a = {}
x = "y"
a[x] = 10 --把10放在字段"y" 中
a[x] --> 10 -- 字段"y"的值
a.x --> nil -- 字段"x"的值(未定义)
a.y --> 10 -- 字段"y"的值
3.3 数字 1 、字符串“+1”、“01”、和“1”指向的是不同的元素,当不清楚表索引的真实数据类型是,可以使用显示类型转换
但是整型和浮点型类型的表索引则不存在上述问题,由于 2 和 2.0的值相等,所以当它们被当作表索引使用时指向的是同一个表元素:
i = 10; j = "10"; k = "+10"
a = {}
a[i] = "number key"
a[j] = "string key"
a[k] = "another string key"
a[i] --> 数值类型的键
a[j] --> 字符串类型的键
a[k] --> 另一个字符串类型的键
a[tonumber(j)] --> 数值类型的键
[tonumber(k)] --> 数值类型的键
-----------整型和浮点型---------
a = {}
a[2.0] = 10
a[2.1] = 20
a[2] --> 10
a[2.1] --> 20
四. 表构造器
表构造器是用来创建和初始化表的表达式,也是Lua 语言中独有的也是最有用、最灵活的机制之一。
4.1 空构造器 {}
表构造器也可以用来初始化列表,例如、下列的中使用字符串“Sunday” 初始化了days[1](构造器第一个元素的索引是1而不是0)、使用字符串“Monday”初始化了days[2],依此类推:
days = {"Sunday", "Monday", "Tuesday", "wednesday", "Thursday", "Friday", "Saturday"}
print(days[4]) --> Wednesday
4.2 初始化记录式表的特殊语法:
a = {x = 10, y = 20} -- 该写法,由于能提前判断表的大小,所以运行速度更快
--等价于:
a = {}; a.x = 10; a.y = 20
4.3 无论使用哪种方式创建表,都可以随时增加或删除表元素:
w = {x = 0, y = 0, label = "console"}
x = {math.sin(0), math.sin(1), math.sin(2)}
w[1] = "another field" -- 把键1增加到表'w'中
x.f = w -- 把键'f'增加到表'x'中
print(w["x"]) --> 0
print(w[1]) --> another field
print(x.f[1]) --> another field
w.x = nil -- 删除字段"x"
4.4 在同一个构造器中,可以混用记录式 和 列表式写法
polyline = {
color = "blue", -- 形如x = y 记录式
thickness = 2,
npoints = 4,
{x = 0, y = 0}, -- polyline[1]
{x = -10, y = 0}, -- polyline[2]
{x = -10, y = 1}, -- polyline[3]
{x = 0, y = 1} -- polyline[4]
}
注意:使用 这两种构造器时,不能使用负数索引初始化表元素,也不能使用不符合规范的标识符作为索引。对于这类需求,可以使用另一种更加通用的构造器,即通过方括号括起来的表达式显示地指定每一个索引:
-- 这种方括号显示指定每一个索引的方法,可以解决 索引不符合规范的问题
opnames = {["+"] = "add", ["-"] = "sub", ["*"] = "mul", ["/"] = "div"}
i = 20;s = "-"
a = {[i + 0] = s, [i + 1] = s..s, [i+2] = s..s..s}
print(opnames[s]) --> sub
print(a[22]) --> ---
五. 数组、列表和序列
声明数组或列表: 只需要保证 索引为 整型即可。
-- 读取10行,然后保存在一个表中
a = {}
for i = 1, 10 do
a[i] = io.read()
end
序列:所有元素都不为nil 的数组称为 序列(sequence)
获取序列长度的操作符# : 该操作符返回表对应序列的长度(如果使用#的表中的元素存在 nil ,即存在空洞时,序列长度操作符是不可靠的,这是个坑)
5.1 长度操作符的几种用法:
5.1.1 输出序列中最后一个值
print(a[#a])
5.1.2 移除最后一个值
a[#a] = nil
5.1.3 把元素加到序列的最后
a[#a + 1] = v
六. 遍历表
使用 pairs迭代器遍历表中的键值对:
t = {10, print, x = 12, k = "hi"}
for k, v in pairs(t) do
print(k, v)
end
--> 1 10
--> k hi
--> 2 function
--> x 12
受限于表在Lua语言中的底层实现机制,遍历过程中元素的出现顺序可能是随机的,相同的程序在每次运行时也可能产生不同的顺序。唯一可以确定的是,在遍历的过程中每个元素会且只会出现一次
对于列表而言,可以使用ipairs迭代器:
t = {10, print, 12, "hi"}
for k, v in ipairs(t) do
print(k, v)
end
--> 1 10
--> 2 function:0x420610
--> 3 12
--> 4 hi
此时,Lua会确保是按顺序进行的
另一种遍历序列的方法是使用数值型for循环:
t = {10, print, 12, "hi"}
for k = 1, #t do
print(t, t[k])
end
--> 1 10
--> 2 function:0x420610
--> 3 12
--> 4 hi
七. 安全访问
考虑如下的情景:我们想确认在指定的库中是否存在某个函数。如果我们确定这个库确实存在,那么可以直接使用 if lib.foo then .... ;否则 使用 if lib and lib.foo then ... 的表达式。
当表的嵌套深度变得比较深时,这种写法就会容易出错,例如
zip = company and company.director and
company.director.address and
company.director.address.zipcode
这种写法不仅冗长并且低效,该写法在一次成功的访问中对表进行了6次访问而非3次访问
C#对安全访问的处理:
在C#中,提供了一种安全访问操作符 ,被记为"?."。例如,对于表达式a ?. b, 当a为nil时,其结果是nil而不会产生异常。使用这种操作符,可以将上例改写为:
zip = company?.director?.address?.zipcode
如果上述的成员访问过程中出现nil,安全访问操作符会正确处理nil 并最终返回nil
Lua语言没有提供安全访问操作符,并且认为也不应该提供这种操作符。我们可以在Lua语言中模拟安全访问操作符。
对于表达式 a or {}, 当a为nil时其结果是一个空表。因此,对于表达式 (a or {}).b,当a为nil时结果也同样时nil。我们可以将之前的例子重写为:
zip = (((company or {}).director or {}).address or {}).zipcode
再进一步,还可以写的更短更高效
E = {}
...
zip = (((company or E).director or E).address or E).zipcode
八. 表标准库
序号 |
方法&用途 |
1 |
table.insert 向序列的指定位置插入一个元素,其他元素依次后移。 |
2 |
table.remove 删除并返回指定序列位置的元素,然后将其后的元素向前移动填充删除元素后的空洞。如果调用函数时不指定位置,该函数会删除序列的最后一个元素 |
3 |
Lua5.3时引入 table.move(a, f, e, t) 调用该函数可以 将表a中从索引f 到 e的元素(包括索引f和索引e对应的元素本身)移动到位置t上。 table.move 还支持使用一个表作为可选的参数。当带有可选的表作为参数时,该函数将第一个表中的元素移动到第二个表中。例如 返回列表a 的一个克隆(clone),通过将列表a中的所有元素拷贝到新列表中。 将列表a中的所有元素复制到列表b的末尾 |