lua元表
这是 Lua 中最核心也最灵活的特性之一 —— 元表本质是给普通 table 附加 “自定义行为” 的特殊 table,比如让 table 支持加减运算、自定义索引查找规则等 元表(Metatab

lua元表

发布时间:2026-01-08 (2026-01-08)

这是 Lua 中最核心也最灵活的特性之一 —— 元表本质是给普通 table 附加 “自定义行为” 的特殊 table,比如让 table 支持加减运算、自定义索引查找规则等

  • 元表(Metatable):一个特殊的 table,用来定义另一个 table(被元表修饰的 table 称为 “原表”)的特殊行为;
  • 元方法(Metamethod):元表中以 __ 开头的键(比如 __add__index),对应原表的自定义行为(比如 __add 定义加法运算);
  • 核心函数
    • setmetatable(t, mt):给 table t 设置元表 mt(返回 t);
    • getmetatable(t):获取 table t 的元表(返回元表或 nil)。

__index(索引查找)

当访问原表中不存在的键时,Lua 会去元表的 __index 中查找,这是 Lua 实现继承、默认值的核心。

__index 可以是 table(优先查找该 table 的键),也可以是函数(自定义查找逻辑)。

-- 示例1:__index 为 table(继承的底层逻辑)
local proto = {name = "默认名称", age = 18}  -- 原型 table
local obj = {gender = "男"}                  -- 原表

-- 给 obj 设置元表,__index 指向 proto
setmetatable(obj, {__index = proto})

-- 访问 obj 不存在的键,自动去 proto 找
print(obj.name)  -- 输出:默认名称(obj 无 name,从 proto 取)
print(obj.age)   -- 输出:18(同理)
print(obj.gender)-- 输出:男(obj 自身有,直接取)

-- 示例2:__index 为函数(自定义查找逻辑)
local t = {}
setmetatable(t, {
    __index = function(table, key)  -- table=原表,key=访问的键
        return "键 " .. key .. " 不存在,返回默认值"
    end
})

print(t.abc)  -- 输出:键 abc 不存在,返回默认值

__newindex(索引赋值)

当给原表中不存在的键赋值时,Lua 会调用元表的 __newindex,而非直接给原表添加键。

  • 用法和 __index 类似,可用于限制赋值、记录赋值日志、重定向赋值目标。
-- 示例:禁止给原表添加新键
local t = {name = "张三"}
setmetatable(t, {
    __newindex = function(table, key, value)
        print("赋值操作:表 =", table, "键 =", key, "值 =", value)
        -- 使用 rawset 绕开元方法,直接给原表赋值(避免递归触发 __newindex)
        rawset(table, key, value)
    end
})

t.age = 20  -- 报错:禁止给表添加新键:age(t 无 age 键,触发 __newindex)
t.name = "李四"  -- 正常执行(t 有 name 键,不触发 __newindex)

__add/__sub/__mul 等

通过元方法自定义原表的算术运算,比如让两个 table 支持加法。常用算术元方法:__add(+)、__sub(-)、__mul(*)、__div(/)、__eq(==)。

-- 示例:让 table 表示向量,支持加法运算
local vec1 = {x = 1, y = 2}
local vec2 = {x = 3, y = 4}

-- 定义元表(包含 __add 元方法)
local vec_mt = {
    __add = function(a, b)  -- a=左操作数,b=右操作数
        return {x = a.x + b.x, y = a.y + b.y}
    end
}

-- 给两个向量设置元表
setmetatable(vec1, vec_mt)
setmetatable(vec2, vec_mt)

-- 向量加法(触发 __add)
local vec3 = vec1 + vec2
print(vec3.x, vec3.y)  -- 输出:4 6

__call(把 table 当作函数调用)

当原表被当作函数调用时(比如 t()),触发 __call 元方法,可实现 “可调用的对象”。

local t = {name = "Lua"}
setmetatable(t, {
    __call = function(table, ...)  -- table=原表,...=调用时的参数
        print("调用了 table,参数:", ...)
        print("table 的 name:", table.name)
    end
})

-- 把 table 当作函数调用(触发 __call)
t(10, 20)
-- 输出:
-- 调用了 table,参数:10 20
-- table 的 name:Lua

__tostring(自定义打印格式)

当用 print() 打印原表时,触发 __tostring 元方法,替代默认的 table: 0xXXXXXXX 格式。

local person = {name = "张三", age = 20}
setmetatable(person, {
    __tostring = function(table)
        return "Person: " .. table.name .. "(" .. table.age .. "岁)"
    end
})

print(person)  -- 输出:Person: 张三(20岁)(而非默认的内存地址)