基本使用
- 可以使用构造器表达式来创建表。例如
-- 创建一个表a a = {} -- 返回表的首地址 a
- 可以向表中添加元素,并通过索引来获取值。例如:
k = "x" a[k] = 10 -- 键为"x", 值为10 a[20] = "great" -- 键为20, 值为"great" a["x"] -- 获取键"x"的值 a[k] -- 同上 a[20] -- 获取键20的值 k = 20 a[k] -- 获取键20的值 a["x"] = a["x"] + 1 --将键"x"的值加1 a["x"]
- 可以有多个表名引用于同一个表,当最后一个引用释放时,垃圾收集器会最终删除这个表。例如:
-- 通过a引用于一个表 a = {} a["x"] = 10 -- b引用与a,指向于同一个表 b = a b["x"] -- b改变表, a也改变 b["x"] = 20 a["x"] -- 释放a的引用,此时表还在 a = nil b -- 释放a的引用,现在没有引用引用于这个表,表释放 b = nil
-- 空的表
a = {}
-- 创建1000个新元素
for i = 1, 100 do a[i] = i*2 end
-- 得到索引9的值
a[9]
-- 设置"x"索引的值为10
a["x"] = 10
a["x"]
-- 表中没有名为"y"的索引, 返回nil
a["y"]
把表当做结构体使用
- 当把表当做结构体使用时,可以把表名当做结构体名,然后使用"."调用表的元素,类似于C语言的结构体用法
- 例如:
-- 创建一个空的表 a = {} -- 等价于a["name"] = "dongshao" a.name = "dongshao" -- 等价于a["age"] = 17 a.age = 17 -- 调用两个结构体成员 a.name a.age -- 没有这个成员, 索引返回nil a.hourse
- 这种用法与普通用法如何选择使用:
- a["age"]、a.age这两种的调用都是等价的,但是如何选择使用呢?
- 通常根据自己的需求而定:
- 如果你的表中的元素通常是固定的、预先定义好的,那么就使用结构体这种调用方法,例如a.age
- 如果你的表可以使用任意值作为键,并且可以表中的元素会动态变化,那么就使用平常的语法,例如a["age"]
索引类型的注意事项
- 索引也是有类型的。例如0和"0"不是同一样,前者是数字类型,后者是字符串类型。所以下面的调用是不等价的:
a = {} -- 这是两个不同的索引键 a[0] = 0 a["0"] = "0"
- 当然,如果你也可以进行显示类型的转换来调用。例如:
a = {} -- 定义两个不同的索引键 a[0] = 0 a["0"] = "0" -- 这两个索引对应的值是不一样的 type(a[0]) type(a["0"]) -- 获取索引值为0的值 a[0] -- 同上 a[tonumber("0")] -- 获取索引值为"0"的值 a["0"] -- 同上 a[tostring(0)]
- 对于整型和浮点型的表索引不会出现上面的问题。如果整型和浮点型之间能够进行转换,那么两者指向的是同一个索引。例如
- 下面通过a[2.0]调用表时,2.0会被转换为2,因此调用的是同一者
a = {} a[2] = 666 a[2] a[2.0] a[2.0] = 777 a[2] a[2.0]
空构造器
- 最简单的构造器是空{}
- 这种构造器初始化的表内容是空的
a = {}
列表式构造器
- 类似于C语言的数组,可以指定元素,其中每个元素对应一个默认的索引
- 默认的索引从1开始(而不是0),并且依次类推
days = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thuray", "Friday", "Saturday"} -- 每个元素都有默认的索引, 从1开始 days[1] days[2] days[3] days[4] days[5] days[6] days[7]
纪录式构造器
- 纪录式构造器可以在定义的时候同时指定索引和对应的值
a = {x = 10, y =20, z = "HelloWorld"} -- 这样是正确的调用 a["x"] -- 这样是错误的, 当前作用域没有名为x的变量 a[x] -- 同上 a["y"] a[y] a["z"]
- 定义这种表时,表的索引有约束:
- 索引的名字必须与Lua变量的命名规则一样,也就是说必须由下划线、字母、数字组成(不能以数字开头)
- 并且定义的时候不能加双引号或者单引号
- 例如:
-- 正确的 a = {x1 = 10} -- 错误的, x1不可以带双引号 a = {"x1" = 10} -- 下面的都是错误的, 索引的名字没有符合变量的命名规则 b = {1b = 20} b = {2c = 20}
"纪录式"与"列表式"混合使用
- 可以将纪录式和列表式的构造器进行混合使用
- 当混合使用时:
- 对于列表式的元素,元素默认索引为1,然后依次类推
- 纪录式元素的索引以其定义的为准
- 例如:
- 下面为纪录式和列表式的混合定义
- coloe、thickness、npoints、value等都是纪录式,定义时直接给出了索引和值
- 其他的都是记录时的,没有给出索引,由表自动给出,索引为1的为{x = 0, y = 0},其也是一个表类型...以此类推
polyline = { color = "blue", thickness = 2, npoints = 4, {x = 0, y = 0}, -- polyline[1] {x = 10, y = 0}, -- polyline[2] 666, -- polyline[3] value = 5, "HelloWorld" -- polyline[4] } print(polyline["color"]) -- blue print(polyline["value"]) -- 5 print(polyline[1].x) -- 0 print(polyline[2].x) -- 10 print(polyline[3]) -- 666 print(polyline[4]) -- HelloWorld
-- 创建一个表w
w = {x = 0, y = 0, label = "console"}
-- 创建一个表x
x = {math.sin(0), math.sin(1), math.sin(2)}
-- 把键1加入到表w中
w[1] = "another field"
-- 把键"f"加入到表x中, 并且键f对应为的元素为表w
x.f = w
w["x"]
w[1]
x[1] -- 调用索引1处的值, 即为math.sin(0)
x.f[1] -- 因为f索引对应的是一个表, 因此我们这就是调用其对应的表中键为1的元素
x["f"][1] -- 同上
-- 删除w的x键
w.x = nil
索引局限性和解决方法(方括号索引)
- 列表式构造器和纪录式构造器有一些局限性,例如:
- 不能使用负数索引初始化元素(索引必须是以1作为开始)
- 纪录式构造器中不能使用不符合规范的标识符作为索引(规定索引的名字必须与变量命名规则一样,见上)
- 纪录式构造器中的索引名不需要使用双引号或者单引号
- 例如,下面是一些错误用例与改正演示:
-- 错误的, +和-不符合索引命名规则 opnames = { + = "add", - = "sub" } -- 错误的,索引虽然正确,但是不需要使用双引号 opnames = { "a" = "add", "b" = "sub" }
- 对于这些局限性,可以使用方括号括起来的表达式显式地指定每一个索引。例如:
-- 正确的,使用方括号指明索引 opnames = { ["+"] = "add", ["-"] = "sub"} -- 调用 opnames["+"] s = "-" opnames[s]
-- 可以使用数字作为索引, 并且索引值还可以为负的 i = 1 a = {[-1] = "A", [0] = "A".."A", [i] = "A".."A".."A"} a[-1] a[0] a[1]
- 可以看出这种方括号形式的索引带来了很多扩展型。例如,下面的几种表达式就相互等价
{x = 0, y = 0} -- 等价于{["x"] = 0, ["y"] = 0} {"r", "g", "b"} -- 等价于{[1] = "r", [2] = "g", [3] = "b"}
构造器的相关注意事项
- 构造器的最后一个元素后面可以跟着一个逗号,但是是可选的,可以不添加
-- 尾部逗号默认省略 a = {[1] = "read", [2] = "green"} -- 同上, 尾部有一个逗号 a = {[1] = "read", [2] = "green",}
- 构造器中的逗号可以用分号代替,这主要是为了兼容Lua语言的旧版本,目前不会被用到。例如:
a = {[1] = "red", [2] = "green"} a[1] a[2] -- 逗号换成分号也正确, 主要是为了兼容旧版本 a = {[1] = "red"; [2] = "green"} a[1] a[2]
数组
- 例如下面声明一个表,然后向里面添加元素,元素的索引都是整型,这有点类似于C语言等的数组
a = {} -- 先输入5个元素 for i = 1, 5 do a[i] = io.read() end -- 打印5个元素 for i = 1, 10 do print(a[i]) end
- 备注:在Lua语言中,数组索引一般从1开始(而不是像C语言一样从0开始),Lua中的其它许多机制也遵循这个管理
- 数组的长度:
- 当操作列表数组时,往往需要知道列表的长度
- 我们可以将列表的长度存放在常量中,或者存放在其它变量或数据结构中,当然也可以把列表的长度保存在表的某个非数值类型的字段中(由于历史原因,这个键常用是"n"),当然列表的长度经常也是隐藏的
- 因为未初始化的元素均为nil,所以可以利用nil来标记列表的结束
序列
- 当一个列表拥有一定数量的元素时,并且列表中不存在空洞时(即所有元素均不为nil,所有索引的值都是连续的),那么我们把这种所有元素都不为nil的数组称为序列
- 例如:
-- 是数组, 但不是序列 a = {[1] = "a", [3] = "c"} -- 是数组, 并且是序列 a = {[1] = "a", [2] = "b", [3] = "c"}
-- 定义一个序列, 获取序列的长度
a = {[1] = "a", [2] = "b", [3] = "c", [4] = "d"}
#a
-- 可以使用序列长度对序列进行操作
a[#a] -- 获取a序列的最后一个元素
a[#a] = nil -- 移除a序列的最后一个元素
a[#a + 1] = "d" -- 把d添加到序列的尾部
a = {10, 20, 30, nil, nil}
pairs迭代器遍历
- 不论列表是否为序列,都可以使用这种遍历方式。例如:
-- 不是序列 t = {10, print, x = 12, k = "hi"} for k, v in pairs(t) do print(k, v) end -- 是个序列, 因为所有元素的索引都有顺序 t = {10, print, 12, "hi"} for k, v in pairs(t) do print(k, v) end
- 受限于Lua语言中的底层实现机制,遍历过程中元素的出现顺序是随机的
ipairs迭代器遍历
- 如果列表是序列,那么可以使用这种方法进行输出;如果表不是序列,那么不可以使用这种输出
- 并且这种输出的顺序是按照表定义时的顺序输出的
- 例如:
-- 不是序列, 只会输出前面连续的内容, 后面的内容不会输出 t = {10, print, x = 12, k = "hi"} for k, v in ipairs(t) do print(k, v) end -- 是序列, 按照顺序输出所有元素 t = {10, print, 12, "hi"} for k, v in ipairs(t) do print(k, v) end
数值型for循环
- 当表为数组类型时,可以通过数值型for循环来输出表中的每一个元素
- 这种循环一般用于序列式的表,因为其会根据索引值和表长度来输出元素
- 例如:
t = {10, print, 12, "hi"} for k = 1, #t do print(k, t[k]) end
zip = company and company.director and
company.director.address and
company.director.address.zipcode
zip = company ?. director ?. address ?. zipcode
这样,我们可以把之前的例子改写为:
zip = (((company or {}).director or {}).address or {}).zipcode
-- 可以在其他类似表达式中复用
E = {}
zip = (((company or E).director or E).address or E).zipcode
table.insert()
- 该函数的一种形式为:向序列的指定位置插入一个元素,其他元素依次后移。例如:
-- 创建一个表, 并打印所有元素 t = {10, 20, 30} for k = 1, #t do print(k, t[k]) end -- 在索引1处插入1个元素 table.insert(t, 1, 15) for k = 1, #t do print(k, t[k]) end
- 该函数的另一种形式为:插入时不指定位置,那么会把新元素插入到序列的尾部。例如:
-- 从标输入中按行读入内容并保存到一个序列中 t = {} for line in io.lines() do table.insert(t, line) end print(#t)
table.remove()
- 该函数的一种形式为:该函数删除并返回序列指定位置的元素,然后将后面的元素向前移动填充。例如:
t = {111, 222, 333} for k = 1, #t do print(k, t[k]) end -- 删除第2个元素 table.remove(t, 2) for k = 1, #t do print(k, t[k]) end
- 该函数的另一种形式为:删除时不指定位置,那么会把末尾的元素进行删除。例如:
t = {111, 222, 333} for k = 1, #t do print(k, t[k]) end -- 删除末尾的元素 table.remove(t) for k = 1, #t do print(k, t[k]) end
利用table.insert()和table.remove()实现堆、队列、双端队列
- 借助这两个函数,可以很容易地实现栈、队列和双端队列
- 以栈为例:
-- 创建一个空栈 t = {} -- 入栈, 在尾部插入元素(push) table.insert(t, x) -- 出栈, 删除尾部的元素(pop) table.remove(t)
table.move()
- Lua 5.3引入了这个函数
- 该函数的一种用法为:table.move(a, f, e, t)。调用该函数可以将表a中从索引f到e的元素(包括f和e本身)拷贝到位置t上
- 例如:下面在列表a的开头插入一个元素
a = {1, 2} -- 将所有元素依次向后移动1位,表会边打 table.move(a, 1, #a, 2) for k = 1, #a do print(k, a[k]) end -- 添加一个元素到头部 a[1] = 10 for k = 1, #a do print(k, a[k]) end
- 例如:下面删除列表a的开头元素
a = {1, 2} -- 将所有元素依次向前移动1位(除了最后一个) table.move(a, 2, #a, 1) for k = 1, #a do print(k, a[k]) end -- 将最后一个元素置为nil a[#a] = nil for k = 1, #a do print(k, a[k]) end
- 移动实际上是将一个值从一个地方拷贝到另一个地方。因此上面的删除操作最后必须显式地把最后一个元素删除
- 该函数的另一种用法为:table.move(a, f, e, t, b)。意思为:把表a从索引f到e的元素(包括f和e本身)移动到表b位置t上,函数返回表b的位置
- 例如:下面把表a的元素移动到另一个空表中,并返回该空表的地址
a = {1, 2} table.move(a, 1, #a, 1, {})
- 例如:下面把表a的所有元素拷贝到表b的尾部
a = {1, 2} b = {3, 4} table.move(a, 1, #a, #b + 1, b)