【读书笔记】语言基础-表(八)

发布于:2022-12-18 ⋅ 阅读:(194) ⋅ 点赞:(0)

目录

注:本系列为《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.1        空构造器 {}

        4.2        初始化记录式表的特殊语法:

        4.3        无论使用哪种方式创建表,都可以随时增加或删除表元素:

        4.4        在同一个构造器中,可以混用记录式 和 列表式写法

五.        数组、列表和序列

        5.1        长度操作符的几种用法:

               5.1.1        输出序列中最后一个值

                5.1.2        移除最后一个值

                5.1.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

向序列的指定位置插入一个元素,其他元素依次后移

t= {10, 20, 30}
table.insert(t, 1, 15)   --> {15, 10, 20, 30}
--不带参数:会在序列最后插入指定的元素,并且不会移动任何元素
table.insert(t, 15)     --> {10, 20, 30, 15}
t = {}
for line in io.lines() do
  table.insert(t, line)
end
  print(#t)  -->(读取的行数)

2

table.remove

删除并返回指定序列位置的元素,然后将其后的元素向前移动填充删除元素后的空洞。如果调用函数时不指定位置,该函数会删除序列的最后一个元素

t = {10, 20, 30}
table.remove(t, 1)
print(t)  --> {20, 30}
table.remove(t)
print(t)  --> {20}

3

Lua5.3时引入 table.move(a, f, e, t)

调用该函数可以 将表a中从索引f 到 e的元素(包括索引f和索引e对应的元素本身)移动到位置t上

--在列表a的开头插入一个元素
table.move(a, 1, #a, 2)
a[1] = newElement
--删除第一个元素
table.move(a, 2, #a, 1)
a[#a] = nil

table.move 还支持使用一个表作为可选的参数。当带有可选的表作为参数时,该函数将第一个表中的元素移动到第二个表中。例如

table.move(a, 1 #a, 1, {})

返回列表a 的一个克隆(clone),通过将列表a中的所有元素拷贝到新列表中。

table.move(a, 1, #a, #b +1, b)

将列表a中的所有元素复制到列表b的末尾

九.        借助 table.insert 和 table.remove 实现 栈(Stack)、队列(Queue)、和双端队列(Double queue)(会在 编程实操-数据结构中详解)

本文含有隐藏内容,请 开通VIP 后查看