如果像表示常见的数组( array
)或列表( list
),那么只需要使用整型作为索引的表即可。同时,也不需要预先声明表的大小,只需要直接初始化我们需要的元素即可:
a = {} for i = 1, 10 do a[i] = io.read() end
鉴于能够使用任意值对表进行索引,我们也可以使用任意数字作为第一个元素的索引。不过,在 Lua
语言中,数组索引按照惯例是从 1
开始的(不像很多语言从 0
开始), Lua
语言中的其他很多机制也遵循这个惯例。
当操作列表时,往往必须事先获取列表的长度。列表的长度可以存放在常量中,也可以存放在其他变量或数据结构中。通常,我们把列表的长度保存在表中某个非数值类型的字段中(由于历史原因,这个键通常是 "n"
)。当然,列表的长度经常也是隐式的。
注意
未经初始化的元素均为 nil
,所以可以利用 nil
值来标记列表结束。
当向一个列表中写入了 10
行数据后,由于该列表的数值类型的索引为 1,2,...,10
,所以可以很容易地知道列表的长度就是 10
。这种技巧只有在列表中不存在空洞( hole
)时(即所有元素均不为 nil
)才有效,此时我们把这种所有元素都不为 nil
的列表称为序列( sequence
)。
Lua
语言提供了获取序列长度的操作符 #
。对于字符串而言,该操作符返回字符串的字节数;对于表而言,该操作符返回表对应序列的长度。例如,可以使用如下的代码输出上例中读入的内容:
for i = 1, #a do print(a[1]) end
长度操作符也为操作序列提供了集中有用的写法:
print(a[#a]) -- 输出序列'a'的最后一个值 a[#a] = nil -- 移除最后一个值 a[#a + 1] = v -- 把'v'加到序列的最后
对于中间存在空洞( nil
值)的列表而言,序列长度操作是不可靠的,它只能用于序列(所有元素均不为 nil
的列表)。更准确的说,序列( sequence
)是由指定的 n
个正整数值类型的键所组成集合 {1, ..., n}
形成的表(请注意值为 nil
的键实际不在表中)。特别的,不包含数值类型键的表就是长度为 0
的序列。
将长度操作符用于存在空洞的列表的行为是 Lua
语言中最具有争议的内容之一。在过去几年中,很多人建议在操作存在空洞的列表时直接抛出异常,也有人建议扩展长度操作符的语义。然而,这些建议都是说起来容易做起来难。其根源在于列表实际上是一个表,而对于表来说,“长度”的概念在一定程度上是不容易理解的。例如,考虑如下代码:
a = {} a[1] = 1 a[2] = nil -- 什么也没做,因为a[2]已经是nil了 a[3] = 1 a[4] = 1
我们可以很容易确定这是一个长度为 4
、在索引 2
的位置上存在空洞的列表。不过,对于下面这个类似的示例是否也是如此呢?
a = {} a[1] = 1 a[10000] = 1
是否应该认为 a
是一个具有 10000
个元素、 9998
个空洞的列表?如果代码进行了如下的操作:
a[10000] = nil
那么列表的长度会变成多少?由于代码删除了最后一个元素,该列表的长度是不是变成了 9999
?或者由于代码只是将最后一个元素变成了 nil
,该列表的长度仍然是 10000
?又或者该列表的长度缩成了 1
?
另一种常见的建议是让#操作符返回表中全部元素的数量。虽然这种语义听起来清晰且定义明确,但并非非常特别有用和符合直觉。
思考
虽然这种语义听起来清晰且定义明确,但并非非常特别有用和符合直觉。为什么这么说?
更复杂的是列表以 nil
结尾的情况:
a = {10, 20, 30, nil, nil}
因为在 Lua
语言中,一个为 nil
的字段和一个不存在的元素没有区别,因此,上述列表与 {10, 20, 30}
是等价的——其长度是 3
而不是 5
。
可以将以 nil
结尾的列表当做一种非常特殊的情况。不过,很多列表是通过逐个添加各个元素创建出来的。任何按照这种方式构造出来的带有空洞的列表,其最后一定存在为 nil
的值。
尽管讨论了这么多,程序中的大多数列表其实都是序列(例如不能为 nil
的文件行)。正因为如此,在多数情况下使用长度操作符是安全的。在确实需要处理存在空洞的列表时,应该将列表的长度显式地保存起来。