元表简介
使用 setmetatable/getmetatable 设置和获取元表。
元表简介 是 CoddyKit 上的免费 Lua Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Lua Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Lua Academy 课程共包含 4 节课。
什么是元表?
元表是一个普通的 Lua 表,用于控制另一个表的行为。当 Lua 遇到无法直接对表执行的操作(例如算术运算或索引不存在的键)时,会在元表中查找对应的元方法。这是 Lua 实现运算符重载和面向对象编程的扩展机制。
local t = {}
local mt = {__index = function(_, k) return "default_" .. k end}
setmetatable(t, mt)
print(t.foo) -- default_foo
print(t.bar) -- default_bar
print(t[1]) -- default_1setmetatable 和 getmetatable
setmetatable(t, mt) 会将 t 的元表设置为 mt,并返回 t。getmetatable(t) 会返回元表;如果没有元表,则返回 nil。将 mt.__metatable = "protected" 设置为 protected,即可防止元表被修改。
local t = {x = 10}
local mt = {}
setmetatable(t, mt)
print(getmetatable(t) == mt) -- true
-- Protect metatable
mt.__metatable = "locked"
print(getmetatable(t)) -- locked
-- setmetatable(t, {}) -- ERROR: cannot change protected metatable作为表的 __index
当 __index 是一个表时,Lua 会在该表中查找缺失的键。这是 Lua 原型继承的基础。如果 __index 中也没有该键,Lua 会检查它自己的元表,并沿着这条链继续查找。
local Animal = {sound = "...", legs = 4}
local Dog = setmetatable({sound = "Woof"}, {__index = Animal})
local Cat = setmetatable({sound = "Meow"}, {__index = Animal})
print(Dog.sound) -- Woof (own field)
print(Dog.legs) -- 4 (from Animal)
print(Cat.sound) -- Meow
print(Cat.legs) -- 4作为函数的 __index
当 __index 是一个函数时,Lua 会将该表和缺失的键作为参数调用它。该函数可以动态计算或获取值。从 __index 返回 nil 表示“未找到键”。
local dynamic = setmetatable({}, {
__index = function(t, k)
if k:sub(1,4) == "get_" then
local field = k:sub(5)
return function(self) return self[field] end
end
return nil
end
})
dynamic.name = "Lua"
local getter = dynamic.get_name
print(getter(dynamic)) -- Luarawget 和 rawset
rawget(t, k) 读取表中的键,但不会触发 __index。rawset(t, k, v) 设置键,但不会触发 __newindex。当您需要在元方法内部直接访问表,以避免无限递归时,可以使用它们绕过元方法。
local logged = setmetatable({}, {
__newindex = function(t, k, v)
print("Setting", k, "=", v)
rawset(t, k, v) -- bypass __newindex
end
})
logged.x = 10 -- Setting x = 10
logged.x = 20 -- no log! key now exists in t
print(logged.x) -- 20共享同一个元表的多个表
一个元表可以由多个表共享。这正是 Lua 类系统的工作方式:一个类的所有实例共享同一个元表,而方法就保存在该元表中。对元表的修改会影响所有实例。
local Mt = {
__index = {
greet = function(self)
print("Hello from " .. (self.name or "?"))
end
}
}
local a = setmetatable({name="Alice"}, Mt)
local b = setmetatable({name="Bob"}, Mt)
a:greet() -- Hello from Alice
b:greet() -- Hello from Bob__metatable 字段
将 mt.__metatable 设置为任意值后,getmetatable 会返回该值,而不是真实的元表。这样可以防止用户检查或替换元表,对于封装和安全性很有用。
local secret = setmetatable({}, {
__metatable = false, -- hide metatable
__index = {value = 42}
})
print(secret.value) -- 42
print(getmetatable(secret)) -- false (not the real mt)
-- setmetatable(secret, {}) -- ERROR检查元表
使用 getmetatable(t) 检查对象是否具有特定的元表(用于类型检查)。由于 __metatable 可以隐藏真实元表,因此请使用 rawequal 或标记字段来可靠地进行实例类型检查。
local MyClass = {}
MyClass.__index = MyClass
MyClass.__metatable = MyClass -- use self as marker
function MyClass.new(x)
return setmetatable({x=x}, MyClass)
end
local obj = MyClass.new(5)
print(getmetatable(obj) == MyClass) -- true
print(obj.x) -- 5非表类型上的元表
Lua 中的字符串已经具有元表(即 string 模块)。因此,您可以对任意字符串调用 s:upper()。在纯 Lua 中,您不能为字符串、数字或布尔值设置元表,只能为表和用户数据设置元表。
local s = "hello"
local mt = getmetatable(s)
-- String metatable has __index = string library
print(mt.__index == string) -- true
-- So all string functions are methods:
print(s:upper()) -- HELLO
print(s:rep(3, "-")) -- hello-hello-hello使用元表进行调试
您可以添加 __tostring 元方法,以控制 tostring() 和 print() 显示表的方式。这对于调试复杂对象非常有价值。
local Point = {}
Point.__index = Point
Point.__tostring = function(p)
return string.format("Point(%g, %g)", p.x, p.y)
end
function Point.new(x, y)
return setmetatable({x=x,y=y}, Point)
end
local p = Point.new(3, 4)
print(tostring(p)) -- Point(3, 4)
print(p) -- Point(3, 4) (print calls tostring)快速检查
当您访问一个表中不存在的键,而该表的元表将 __index 设置为另一个表时,会发生什么?
回顾:元表
总结:
setmetatable(t, mt)/getmetatable(t)__index:表 → 原型查找;函数 → 动态分派rawget/rawset会绕过元方法- 共享元表可以实现类似类的行为
__metatable可以保护元表不被检查__tostring控制打印输出
常见问题解答
「元表简介」课时是免费的吗?
是的 — 「元表简介」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Lua Academy 课程的其余内容,请升级到 CoddyKit PRO。 Lua Academy 课程共包含 4 节课。
「元表简介」这节课中我会学到什么?
使用 setmetatable/getmetatable 设置和获取元表。 你通过在浏览器中直接运行的动手代码来练习 Lua Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Lua Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Lua Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「元表简介」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Lua Academy 课中编写并运行代码吗?
能。每节 Lua Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。