首先,您应该了解 Lua 表的真正含义。从表面上看,它们表现为关联数组或映射(两个广为人知的 CS 术语),通常以哈希表的形式实现。之所以这样称呼它们,是因为它们将一个所谓的键(唯一的)与一个值相关联。它们可以被视为由键索引的键值对的集合,即以使用(唯一)键可以非常快速地找到相应值的方式实现。
Lua 表的两个主要特点,也使它们真正强大和灵活的数据结构(虽然一开始完全理解有点棘手),如下所示:
Lua 表允许任何类型的值(除了,nil
)作为键。在大多数语言中,键被限制为字符串,或者是必须提前声明的特定类型。在 Lua 中,表可以随时保存任何类型的值作为键。因此像这样定义的表在 Lua 中是完全合法的:
f = function(x) return x*x end
t = { 1, 2, 3 }
tbl = {
[t] = true, -- `t` is the key (and a table), `true` is the value
[f] = 12, -- `f` is the key (and a function), `12` is the value
[true] = f, -- `true` is the key (boolean), `f` now is used as value
[12] = f, -- `12` is the key (number), `f` again as the value
["yup"] = t, -- `"yup"` is the key (string), `t` now is used as value
}
正整数键具有特殊状态,因为它们用于模拟数组。Lua 没有正确的数组概念。在 Lua 中,我们使用类似数组的表(又名序列,使用新的 Lua 5.2 术语)。很多时候,当您在 Lua 的上下文中看到术语数组时,实际上 writer 的意思是类似数组的 table,为了简单起见,我将在下面这样做,以免产生歧义。Lua 中的数组是什么?它是一个表,其正整数键以某个整数开始1
和结束n
, 即其正整数键只有数字 1, 2, ..., n(另一种说法是正整数键形成集合 {1, 2,...,n})。该数字n
称为序列(数组)的长度,它是#
运算符在应用于数组时返回的数字。如果表具有此属性,则称为序列,即可以称为数组。请注意,具有该属性的表仍然是一个数组,如果:
- 它有额外的非数字键(例如字符串或表格键);
- 它有额外的非整数数字键(例如
1.23
);
- 它有额外的非正整数键(例如
0
or -12
)。
“通用表”和数组之间的区别不仅仅是术语上的方便。在底层,Lua 实现会识别一个表是否确实是一个数组,并执行一些优化以允许 Lua 表在将类似数组的表用作数组时具有高性能(例如在 C 中的含义)。事实上 Lua 标准table
库假定提供给它的函数的表(如table.sort
)确实是数组,并且只对具有正整数索引的条目进行操作。
考虑到所有这些,我们可以分析您发布的代码中的难点。
a = {}
for n in pairs(t) do
a[#a + 1] = n
end
这是一个通用 for 循环的示例。pairs
返回(除其他外)表迭代器函数(因此pairs
可以ipairs
称为迭代器生成函数或迭代器生成器)。这个迭代器函数被for
机器重复调用以迭代所有的键(和相应的值)t
。由于for
( 即n
) 中只出现一个变量,t
因此在迭代期间只检索 的键。
a[#a + 1] = n
只是将存储的键附加n
到 table的一种快速方法a
,它原来是一个数组,因为它是在迭代过程中逐步构建的,只有从 1 开始的顺序正整数键。记住这#a
是的当前长度a
(最初是0
,因为a
没有条目),因此a[#a+1]
使用整数键创建一个新条目,而#a + 1
不会破坏 的序列属性。a
总而言之,该for
循环只是收集t
数组a
中的所有键,以便使用它们对它们进行排序table.sort
,然后打印它们:
table.sort(a)
for _, n in ipairs(a) do
print (n)
end
前面是另一个泛型的例子。在这种情况下,由返回的迭代器函数ipairs
将返回(正整数)键和a
迭代期间的值(按此顺序)。由于我们只对打印值感兴趣(键将是1
, 2
, ... 等,因为a
它是一个数组),所以我们将_
其用作虚拟变量来获取(与我们无关的)键。我们本可以使用其他名称,但在 Lua 中使用(完全合法且正常的)名称_
来完成此任务是惯用的。
的定义pairsByKeys
有点难以分析。它的目的是让一个迭代器生成器(pairsByKeys
)返回一个迭代器函数,该函数可以迭代一个表,以保证迭代是根据特定的键顺序完成的(Luapairs
不保证任何特定的迭代顺序)。它应该像这样使用:
for k, v in pairsByKeys( t ) do
print( k, v )
end
我们来分析一下定义。我们将看到它将我们已经分析过的代码逻辑打包在一个函数中(加上一个增强功能)。
function pairsByKeys(t,f)
local b = {}
for x in pairs(t) do
b[#b + 1] = x
end
table.sort(b, f)
local i = 0
return function()
i = i + 1
return b[i], t[b[i]]
end
end
首先要注意的是它pairsByKeys
返回一个函数(迭代器),它实际上是一个具有三个上值( 和 )的匿名i
闭t
包b
。这意味着返回的函数将能够在机器执行时引用这三个变量(此闭包是有状态迭代器for
的示例)。
在返回迭代器之前,pairsByKeys
“预处理”要迭代的表,t
提取其键并对它们进行排序,正如我们在上面已经看到的那样。因此将按放置它们的顺序b
保存所有键。请注意,此调用有一个附加参数,它是一个比较器函数,可以在调用时指定。这允许根据不同的标准对键进行排序(这是我告诉你的“增强”)。t
table.sort(b,f)
table.sort
f
pairsByKeys
该变量将保存刚刚被迭代i
的键的索引。b
因为在这个阶段没有发生迭代(迭代器还没有创建)。
现在让我们关注迭代器函数:
function()
i = i + 1
return b[i], t[b[i]]
end
每当for
机器调用 this 时,它会递增i
,然后它会 fetch b[i]
,这是要迭代的下一个键(它从它获取它,b
因为它b
保存有关它们的排序信息),然后它b[i]
再次使用它来获取相应的值从包含此信息t[b[i]]
的原始表中。t
键和值都被返回,这两个值是(在每次迭代中)分配给循环变量k
和v
上面示例的值,然后打印它们。