0Pricing
Lua Academy · 课时

元表简介

使用 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_1

setmetatable 和 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))   -- Lua

rawget 和 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 反馈 — 无需本地设置。

此课程中的所有课时

  1. 元表简介
  2. __index 和 __newindex
  3. 算术元方法
  4. __tostring 和 __len
← 返回 Lua Academy