第一版是为 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。保留所有权利。 |