在 C++ 中,我們可以透過運算子多載讓兩個類別相加;同樣地,Lua 雖然不支援直接對兩個 table 進行「+」運算,但我們可以透過元表與元方法來實現這一機制。本文將系統整理 Lua 中元表的核心概念、常見元方法的用法,並搭配完整程式碼示範,幫助你掌握這項進階技巧。
Lua 中每個值都擁有一組預定義操作(如數字可相加、字串可連接),但 table 預設不支援算術運算。元表就是一個普通的 table,透過 setmetatable 關聯到目標 table,當發生非預定義操作(如 a + b)時,Lua 會依序檢查:
__add)?table 和 userdata 可各自擁有獨立元表,其他型別則共享型別層級的單一元表。字串在標準庫載入後會自動擁有元表,數字等基本型別則預設無元表。
local t = {1, 2}
print(getmetatable(t)) -- nil
local mt = {}
setmetatable(t, mt)
assert(getmetatable(t) == mt)
任何 table 皆可作為元表,甚至可自我作為元表。僅能在 Lua 程式碼中設定 table 的元表,其他型別需透過 C API 設定。
| 元方法 | 對應操作 |
|---|---|
__add | 加法 (+) |
__sub | 減法 (-) |
__mul | 乘法 (*) |
__div | 除法 (/) |
__mod | 取模 (%) |
__pow | 乘冪 (^) |
__unm | 相反數 (-a) |
__concat | 連接 (..) |
__len | 長度 (#) |
__eq | 相等 (==) |
__lt | 小於 (<) |
__le | 小於等於 (<=) |
__index | 索引查詢 |
__newindex | 索引更新 |
__call | 函式呼叫 |
__tostring | 字串轉換 |
__metatable | 保護元表 |
以下示範如何用 __add 實現集合並集:
Set = {}
local mt = {}
function Set.new(l)
local set = {}
setmetatable(set, mt)
for _, v in pairs(l) do set[v] = true end
return set
end
function Set.union(a, b)
local res = Set.new{}
for v in pairs(a) do res[v] = true end
for v in pairs(b) do res[v] = true end
return res
end
mt.__add = Set.union
local s1 = Set.new{10, 20, 30}
local s2 = Set.new{1, 2}
local s3 = s1 + s2 -- 自動呼叫 Set.union
為避免型別不符導致難以除錯,建議在元方法中檢查元表一致性:
function Set.union(a, b)
if getmetatable(a) ~= mt or getmetatable(b) ~= mt then
error("metatable error: 僅支援 Set 之間的運算")
end
-- ...
end
mt.__tostring = Set.toString
print(s1) -- {10, 20, 30}
mt.__metatable = "You cannot access the metatable"
print(getmetatable(s1)) -- You cannot access the metatable
setmetatable(s1, {}) -- error: cannot change a protected metatable
當索引不存在時觸發,可為函式或 table:
Window = {}
Window.default = {x=0, y=0, w=100, h=100}
Window.mt = {}
function Window.new(o)
setmetatable(o, Window.mt)
return o
end
Window.mt.__index = Window.default -- 直接指向預設值表
local w = Window.new{x=10, y=20}
print(w.w) -- 100 (來自 default)
對不存在的索引賦值時觸發,同樣支援函式或 table 導向。注意避免循環參考導致 loop in settable 錯誤。
rawget(t, k) -- 直接讀取,不觸發 __index
rawset(t, k, v) -- 直接寫入,不觸發 __newindex
元表與元方法是 Lua 物件導向與運算子多載的基石。掌握 __index/__newindex 可實現繼承與預設值,__add 等算術元方法賦予 table 數學語意,__tostring/__metatable 則提升除錯體驗與封裝性。深入理解這些機制,是玩轉 Lua 的關鍵。