通过元表实现类
使用表和 __index 模拟类,并查找方法。
通过元表实现类 是 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 反馈 — 无需本地设置。