第一版是为 Lua 5.0 编写的。虽然在很大程度上仍然适用于更高版本,但有一些差异。
第四版针对 Lua 5.3,可在 Amazon 和其他书店购买。
购买本书,您还可以帮助支持 Lua 项目


13.1 – 算术元方法

在本节中,我们将介绍一个简单的示例来说明如何使用元表。假设我们使用表来表示集合,并使用函数来计算两个集合的并集、交集等。与列表一样,我们将这些函数存储在表中,并定义一个构造函数来创建新集合

    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