概念

每个 table 可以挂一个 metatable——里面定义"特殊行为"。当 Lua 看见某些操作(加法、索引、调用…)时,去 metatable 查 __xxx 元方法。

local t = {}
local mt = {}
setmetatable(t, mt)

-- 现在 t 的"魔法方法"在 mt 里

setmetatable(table, metatable) / getmetatable(table)

__index:找不到 key 时怎么办

local defaults = { color = "red", size = "M" }
local item = setmetatable({}, { __index = defaults })

print(item.color)    -- "red"(从 defaults 继承)
item.color = "blue"
print(item.color)    -- "blue"(自己有了)
print(defaults.color)-- "red"(没被改)

__index 是面向对象继承的核心。可以是表(如上)或函数:

local proxy = setmetatable({}, {
    __index = function(t, k) return "key=" .. k end
})
print(proxy.foo)    -- "key=foo"
print(proxy.bar)    -- "key=bar"

__newindex:赋值新 key 时怎么办

local readonly = setmetatable({x = 1}, {
    __newindex = function(t, k, v)
        error("不许改:" .. k, 2)
    end
})

print(readonly.x)        -- 1
readonly.y = 2           -- 报错
readonly.x = 99          -- 不报错(x 已存在,不走 __newindex)

只在新 key触发——已存在的 key 直接覆盖,不查 metatable。

要拦截所有赋值:用空 table 当外壳,把数据放 metatable。

运算符元方法

元方法 触发
__add a + b
__sub a - b
__mul __div __mod __pow 乘除模幂
__unm -a
__concat a .. b
__len #a
__eq __lt __le 比较
__band __bor __bxor __bnot __shl __shr 位运算(5.3+)

例:向量

local Vec = {}
Vec.__index = Vec
Vec.__add = function(a, b) return Vec.new(a.x + b.x, a.y + b.y) end
Vec.__tostring = function(v) return string.format("(%d, %d)", v.x, v.y) end

function Vec.new(x, y)
    return setmetatable({x = x, y = y}, Vec)
end

local v1 = Vec.new(1, 2)
local v2 = Vec.new(3, 4)
print(v1 + v2)      -- (4, 6)

__call:把 table 当函数

local t = setmetatable({}, {
    __call = function(self, x) return x * 2 end
})
print(t(5))     -- 10

适合工厂、单例、callable 对象。

__tostring:自定义 print

local t = setmetatable({x=1, y=2}, {
    __tostring = function(t) return "Point("..t.x..","..t.y..")" end
})
print(t)        -- Point(1,2)

__metatable:保护 metatable

setmetatable(t, {__metatable = "locked"})
getmetatable(t)        -- "locked"(不是真 metatable)
setmetatable(t, {})    -- 报错

防止外部篡改。

__gc:垃圾回收前回调

local file = setmetatable({}, {
    __gc = function(self) print("released") end
})
file = nil
collectgarbage()        -- "released"

清理外部资源(如 C 句柄)用。只对 userdata 和 table(5.2+)生效,且不要在里面 setmetatable 等危险操作。

__index 链

local A = {a = 1}
local B = setmetatable({b = 2}, {__index = A})
local C = setmetatable({c = 3}, {__index = B})

print(C.a)    -- 1(从 C → B → A 找到)
print(C.b)    -- 2
print(C.c)    -- 3

这是 Lua "继承" 的本质——一串 __index 委托。

→ 下一篇 面向对象写法