这是 Lua 中最核心也最灵活的特性之一 —— 元表本质是给普通 table 附加 “自定义行为” 的特殊 table,比如让 table 支持加减运算、自定义索引查找规则等
- 元表(Metatable):一个特殊的 table,用来定义另一个 table(被元表修饰的 table 称为 “原表”)的特殊行为;
- 元方法(Metamethod):元表中以
__开头的键(比如__add、__index),对应原表的自定义行为(比如__add定义加法运算); - 核心函数:
setmetatable(t, mt):给 tablet设置元表mt(返回t);getmetatable(t):获取 tablet的元表(返回元表或 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岁)(而非默认的内存地址)