第一版是为 Lua 5.0 编写的。虽然在很大程度上仍然适用于更高版本,但有一些差异。
第四版针对 Lua 5.3,可在 Amazon 和其他书店购买。
购买本书,您还可以帮助支持 Lua 项目。
![]() |
用 Lua 编程 | ![]() |
| 第二部分。表和对象 第 13 章。元表和元方法 |
在本节中,我们将介绍一个简单的示例来说明如何使用元表。假设我们使用表来表示集合,并使用函数来计算两个集合的并集、交集等。与列表一样,我们将这些函数存储在表中,并定义一个构造函数来创建新集合
Set = {}
function Set.new (t)
local set = {}
for _, l in ipairs(t) do set[l] = true end
return set
end
function Set.union (a,b)
local res = Set.new{}
for k in pairs(a) do res[k] = true end
for k in pairs(b) do res[k] = true end
return res
end
function Set.intersection (a,b)
local res = Set.new{}
for k in pairs(a) do
res[k] = b[k]
end
return res
end
为了帮助检查我们的示例,我们还定义了一个函数来打印集合
function Set.tostring (set)
local s = "{"
local sep = ""
for e in pairs(set) do
s = s .. sep .. e
sep = ", "
end
return s .. "}"
end
function Set.print (s)
print(Set.tostring(s))
end
现在,我们希望加法运算符 (`+´) 计算两个集合的并集。为此,我们将安排所有表示集合的表共享一个元表,并且此元表将定义它们如何对加法运算符做出反应。我们的第一步是创建一个常规表,我们将其用作集合的元表。为了避免污染我们的命名空间,我们将其存储在 Set 表中
Set.mt = {} -- metatable for sets
下一步是修改 Set.new 函数,该函数创建集合。新版本只有一行额外的代码,它将 mt 设置为它创建的表的元表
function Set.new (t) -- 2nd version
local set = {}
setmetatable(set, Set.mt)
for _, l in ipairs(t) do set[l] = true end
return set
end
之后,我们使用 Set.new 创建的每个集合都将具有相同的表作为其元表
s1 = Set.new{10, 20, 30, 50}
s2 = Set.new{30, 1}
print(getmetatable(s1)) --> table: 00672B60
print(getmetatable(s2)) --> table: 00672B60
最后,我们向元表添加所谓的元方法,即一个字段 __add,它描述如何执行并集
Set.mt.__add = Set.union每当 Lua 尝试添加两个集合时,它都会调用此函数,并使用两个操作数作为参数。
有了元方法,我们可以使用加法运算符来执行集合并集
s3 = s1 + s2
Set.print(s3) --> {1, 10, 20, 30, 50}
类似地,我们可以使用乘法运算符来执行集合交集
Set.mt.__mul = Set.intersection
Set.print((s1 + s2)*s1) --> {10, 20, 30, 50}
对于每个算术运算符,元表中都有一个对应的字段名。除了 __add 和 __mul 之外,还有 __sub(用于减法)、__div(用于除法)、__unm(用于取反)和 __pow(用于求幂)。我们还可以定义字段 __concat,为连接运算符定义一种行为。
当我们添加两个集合时,无需考虑使用哪个元表。但是,我们可能会编写一个表达式,将具有不同元表的两个值混合在一起,例如这样
s = Set.new{1,2,3}
s = s + 8
为了选择一个元方法,Lua 执行以下操作:(1) 如果第一个值具有带有 __add 字段的元表,Lua 将此值用作元方法,与第二个值无关;(2) 否则,如果第二个值具有带有 __add 字段的元表,Lua 将此值用作元方法;(3) 否则,Lua 会引发错误。因此,最后一个示例将调用 Set.union,表达式 10 + s 和 "hy" + s 也将调用 Set.union。
Lua 并不关心这些混合类型,但我们的实现关心。如果我们运行 s = s + 8 示例,我们收到的错误将位于 Set.union 中
bad argument #1 to `pairs' (table expected, got number)如果我们想要更清晰的错误消息,我们必须在尝试执行操作之前显式检查操作数的类型
function Set.union (a,b)
if getmetatable(a) ~= Set.mt or
getmetatable(b) ~= Set.mt then
error("attempt to `add' a set with a non-set value", 2)
end
... -- same as before
| 版权所有 © 2003–2004 Roberto Ierusalimschy。保留所有权利。 | ![]() |