Lua Academy · 课时

通过元表实现类

使用表和 __index 模拟类,并查找方法。

第 1 / 4 课12 个步骤

通过元表实现类 是 CoddyKit 上的免费 Lua Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Lua Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Lua Academy 课程共包含 4 节课。

类表模式

在 Lua 中,“类”只是一个存储方法的表。实例是这样的表:其元表的 __index 指向类表。通过查找链,实例就可以访问所有方法。

local Animal = {}
Animal.__index = Animal

function Animal.new(name, sound)
  return setmetatable({name=name, sound=sound}, Animal)
end

function Animal:speak()
  print(self.name .. " says " .. self.sound)
end

local dog = Animal.new("Rex","Woof")
dog:speak()  -- Rex says Woof

__index 指向类

设置 ClassName.__index = ClassName 意味着“在类表中查找缺失的键”。调用实例方法时,Lua 会先在实例中查找;找不到后检查元表(__index),找到类表,再从类表中查找该方法。

local Point = {}
Point.__index = Point

Point.type = "2D Point"

function Point.new(x,y)
  return setmetatable({x=x,y=y}, Point)
end

function Point:magnitude()
  return math.sqrt(self.x^2 + self.y^2)
end

local p = Point.new(3,4)
print(p:magnitude())   -- 5.0
print(p.type)          -- 2D Point (from class)

构造函数模式

new() 函数就是构造函数:它创建一个包含实例数据的表,并将其包装在指向类表的元表中后返回。这是 Lua 中创建对象的标准方式。

local Circle = {}
Circle.__index = Circle

function Circle.new(cx, cy, radius)
  assert(radius > 0, "radius must be positive")
  return setmetatable({cx=cx, cy=cy, r=radius}, Circle)
end

function Circle:area()
  return math.pi * self.r^2
end

function Circle:perimeter()
  return 2 * math.pi * self.r
end

local c = Circle.new(0, 0, 5)
print(string.format("Area: %.2f", c:area()))

方法语法与函数语法

obj:method() 是 obj.method(obj) 的简写。使用冒号语法定义方法(function Cls:method()),即可隐式获得 self。调用时也使用冒号,以传入实例。混用点号和冒号会导致隐蔽的错误。

local Dog = {}
Dog.__index = Dog

-- Define with colon: self is implicit
function Dog:bark(times)
  for i = 1, (times or 1) do
    print(self.name .. ": Woof!")
  end
end

function Dog.new(name)
  return setmetatable({name=name}, Dog)
end

local d = Dog.new("Buddy")
d:bark(2)        -- Buddy: Woof! (x2)
-- Dog.bark(d,1) -- same thing, explicit self

类级字段与实例级字段

类表中的字段由所有实例共享(类变量)。实例表中的字段则属于各自的实例。实例可以创建同名字段,从而遮蔽类字段。

local Entity = {}
Entity.__index = Entity
Entity.count = 0    -- class-level counter

function Entity.new(name)
  Entity.count = Entity.count + 1
  return setmetatable({name=name, id=Entity.count}, Entity)
end

local a = Entity.new("alpha")
local b = Entity.new("beta")
local c = Entity.new("gamma")

print(Entity.count)  -- 3
print(a.id, b.id, c.id)  -- 1  2  3

类的 tostring

将 __tostring 添加到类表中,以获得易读的打印输出。由于实例的元表就是类表,而类表包含 __tostring,所有实例都会自动使用它。

local Vec2 = {}
Vec2.__index = Vec2
Vec2.__tostring = function(v)
  return string.format("Vec2(%g, %g)", v.x, v.y)
end

function Vec2.new(x,y)
  return setmetatable({x=x,y=y}, Vec2)
end

function Vec2.__add(a, b)
  return Vec2.new(a.x+b.x, a.y+b.y)
end

local v1 = Vec2.new(1,2)
local v2 = Vec2.new(3,4)
print(v1+v2)   -- Vec2(4, 6)

检查实例类型

将对象的元表与类表进行比较,即可检查对象是否为某个类的实例。这相当于 Lua 中的 instanceof。

local Dog = {}
Dog.__index = Dog

function Dog.new(name)
  return setmetatable({name=name}, Dog)
end

local function isDog(obj)
  return getmetatable(obj) == Dog
end

local d = Dog.new("Rex")
local t = {}
print(isDog(d))  -- true
print(isDog(t))  -- false

私有方法

私有方法是模块文件中的局部函数。它们无法从外部调用,但可以作为上值供模块的公共方法访问。

local Account = {}
Account.__index = Account

-- Private helper
local function validate(amount)
  return type(amount)=="number" and amount > 0
end

function Account.new(balance)
  return setmetatable({balance=balance or 0}, Account)
end

function Account:deposit(amount)
  assert(validate(amount), "invalid amount")
  self.balance = self.balance + amount
end

function Account:getBalance() return self.balance end

类比较

重载 __eq 和 __lt,以实现自定义的相等性和排序规则。对于表,只有当两个操作数共享同一个元表时,Lua 才会调用 __eq。

local Temp = {}
Temp.__index = Temp
Temp.__eq = function(a,b) return a.celsius == b.celsius end
Temp.__lt = function(a,b) return a.celsius < b.celsius end
Temp.__tostring = function(t)
  return t.celsius.."°C"
end

function Temp.new(c)
  return setmetatable({celsius=c}, Temp)
end

local t1 = Temp.new(100)
local t2 = Temp.new(0)
print(t1 == Temp.new(100))  -- true
print(t2 < t1)              -- true

多个类

每个类都是一个独立的表,并拥有自己的元表。不同类创建的对象具有不同的元表。这是 Lua 多类面向对象系统的基础。

local Cat = {}
Cat.__index = Cat

function Cat.new(name) return setmetatable({name=name,lives=9},Cat) end
function Cat:loseLife() self.lives=self.lives-1; return self.lives end

local Dog2 = {}
Dog2.__index = Dog2
function Dog2.new(name) return setmetatable({name=name,tricks={}},Dog2) end
function Dog2:learn(t) self.tricks[#self.tricks+1]=t end

local c = Cat.new("Whiskers")
local d = Dog2.new("Buddy")
d:learn("sit"); d:learn("stay")
print(c:loseLife(), #d.tricks)  -- 8  2

快速检查

为什么要设置 ClassName.__index = ClassName?

回顾:通过元表实现类

总结:

  • 类 = 表 + __index = self
  • 构造函数:function Cls.new(...) return setmetatable({}, Cls) end
  • 方法:定义在类表中,并使用冒号语法调用
  • 类字段与实例字段:类字段共享,实例字段属于各个对象
  • 类型检查:getmetatable(obj) == Cls
免费开始

用 AI 导师学习 Lua — 免费

在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。

课程
40
课程
159

常见问题解答

「通过元表实现类」课时是免费的吗?

是的 — 「通过元表实现类」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Lua Academy 课程的其余内容,请升级到 CoddyKit PRO。 Lua Academy 课程共包含 4 节课。

「通过元表实现类」这节课中我会学到什么?

使用表和 __index 模拟类,并查找方法。 你通过在浏览器中直接运行的动手代码来练习 Lua Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Lua Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Lua Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。

「通过元表实现类」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Lua Academy 课中编写并运行代码吗?

能。每节 Lua Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 通过元表实现类
  2. 构造函数和 new()
  3. 实例方法和 self
  4. 使用闭包实现封装
← 返回 Lua Academy