第一版是为 Lua 5.0 撰写的。虽然在很大程度上仍然适用于后续版本,但有一些区别。
第四版针对 Lua 5.3,可在 亚马逊 和其他书店购买。
通过购买这本书,您还可以帮助 支持 Lua 项目


16.1 – 类

用作创建对象的模具。多种面向对象语言提供类的概念。在这些语言中,每个对象都是特定类的实例。Lua 没有类的概念;每个对象都定义自己的行为并具有自己的形状。尽管如此,在基于原型的语言(如 Self 和 NewtonScript)的指导下,在 Lua 中模拟类并不困难。在这些语言中,对象没有类。相反,每个对象可能有一个原型,这是一个常规对象,第一个对象会在其中查找它不知道的任何操作。为了在这些语言中表示一个类,我们只需创建一个对象,专门用作其他对象(其实例)的原型。类和原型都用作放置多种对象共享的行为的地方。

在 Lua 中,使用我们在上一章中看到的继承思想来实现原型非常简单。更具体地说,如果我们有两个对象 ab,我们所要做的就是让 b 成为 a 的原型,即

    setmetatable(a, {__index = b})
之后,a 会在 b 中查找它不具备的任何操作。将 b 视为对象 a 的类,这只不过是术语上的改变。

让我们回到银行账户的示例。为了创建行为类似于 Account 的其他账户,我们安排这些新对象从 Account 继承其操作,使用 __index 元方法。请注意一个小优化,我们不需要创建一个额外的表作为账户对象的元表;我们可以将 Account 表本身用于此目的

    function Account:new (o)
      o = o or {}   -- create object if user does not provide one
      setmetatable(o, self)
      self.__index = self
      return o
    end
(当我们调用 Account:new 时,self 等于 Account;因此我们可以直接使用 Account,而不是 self。但是,在下一节中引入类继承时,使用 self 会非常合适。) 在这段代码之后,当我们创建一个新账户并在其上调用一个方法时,会发生什么?
    a = Account:new{balance = 0}
    a:deposit(100.00)
当我们创建这个新账户时,a 将拥有 Account(调用 Account:new 中的 self)作为其元表。然后,当我们调用 a:deposit(100.00) 时,我们实际上是在调用 a.deposit(a, 100.00)(冒号只是语法糖)。但是,Lua 无法在表 a 中找到 "deposit" 条目;因此,它会查找元表的 __index 条目。现在的情况或多或少是这样的
    getmetatable(a).__index.deposit(a, 100.00)
a 的元表是 AccountAccount.__index 也是 Account(因为新方法执行了 self.__index = self)。因此,我们可以将之前的表达式重写为
    Account.deposit(a, 100.00)
也就是说,Lua 调用原始的 deposit 函数,但将 a 作为 self 参数传递。因此,新账户 aAccount 继承了 deposit 函数。通过相同的机制,它可以从 Account 继承所有字段。

继承不仅适用于方法,还适用于新账户中不存在的其他字段。因此,类不仅提供方法,还为其实例字段提供默认值。请记住,在我们对 Account 的第一个定义中,我们提供了一个值为 0 的字段 balance。因此,如果我们在没有初始余额的情况下创建新账户,它将继承此默认值

    b = Account:new()
    print(b.balance)    --> 0
当我们在 b 上调用 deposit 方法时,它运行等效于
    b.balance = b.balance + v
(因为 selfb)。表达式 b.balance 求值为零,并且初始存款被分配给 b.balance。下次我们询问此值时,将不会调用索引元方法(因为现在 b 有自己的 balance 字段)。