Lua 中的元表和元方法
Lua中每個值都可具有元表。 元表是普通的Lua表,定義了原始值在某些特定操作下的行為。你可通過在值的原表中設定特定的欄位來改變作用於該值的操作的某些行為特徵。例如,當數字值 作為加法的運算元時,Lua檢查其元表中的"__add"欄位是否有個函式。如果有,Lua呼叫它執行加法。
我們稱元表中的鍵為事件(event),稱值為元方法(metamethod)。前述例子中的事件是"add",元方法是執行加法的函式。
可通過函式getmetatable查詢任何值的元表。
可通過函式setmetatable替換表的元表。不能從Lua中改變其他型別的元表(除了使用除錯庫);必須使用C API才能做到。
表和完整的使用者資料具有獨立的元表(儘管多個表和使用者資料可共享元表);每種其他型別的所有值共享一個元表。所以,所有數字共享一個元表,字串也 是,等等。
元表可以控制物件的數學運算、順序比較、連線、取長、和索引操作的行為。元表也能定義使用者資料被垃圾收集時呼叫的函式。Lua給這些操作的每一個都 關聯了稱為事件的特定鍵。當Lua對某值執行其中一個操作時,檢查該值是否含有元表以及相應的事件。如果有,與該鍵關聯的值(元方法)控制Lua如何完成 操作。
元表控制後面列舉的操作。每個操作由相應的名字標識。每個操作的鍵是由其名字字首兩個下劃線“__”的字串;例如,操作“加(add)”的鍵是字 符串"__add"。這些操作的語義通過一個Lua函式描述直譯器如何執行操作作了更好的說明。
下面顯示的Lua程式碼只是說明性的;真實的行為被硬編碼到直譯器中,並且比這裡的模擬更加高效。這些描述中的所有函式(rawget、 tonumber等等。)在§5.1中描述。特別一提,要獲取給定物件的元方法,我們使用表示式
metatable(obj)[event]
它應被解讀為
rawget(getmetatable(obj) or {}, event)
就是說,訪問一個元方法不會呼叫其他元方法,而且訪問沒有元表的物件不會失敗(只是結果為nil)。
"add": + 操作。
下面的
getbinhandler函式定義Lua如何選擇二元操作的處理程式。首先嚐試第一運算元,如果它的型別沒有定義該操作的處理程式,則嘗試第二運算元。
function
getbinhandler (op1, op2, event)
return metatable(op1)[event] or
metatable(op2)[event]
end
通過應用該函式,op1 + op2的行為是
function
add_event (op1, op2)
local o1, o2 = tonumber(op1), tonumber(op2)
if o1 and o2
then -- 兩運算元都是數字
return o1 + o2 -- ‘+’此處是‘add’的原語
else --
至少一個運算元不是數字
local h = getbinhandler(op1, op2, "__add")
if h then
-- 用兩個運算元呼叫處理程式
return (h(op1, op2))
else --
沒有可用的處理程式:預設行為
error(...)
end
end
end
"sub": - 操作。
行為類似於“add”操作。
"mul": * 操作。
行為類似於“add”操作。
"div": / 操作。
行為類似於“add”操作。
"mod": % 操作。
行為類似於“add”操作。以o1 - floor(o1/o2)*o2為操作原語。
"pow": ^ (取冪)操作。
行為類似於“add”操作,以函式pow(來自C數學庫)為操作原語。
"unm": 一元-操作。
function unm_event (op)
local o =
tonumber(op)
if o then -- 運算元是數字?
return -o --
‘-’此處是‘unm’的原語
else -- 運算元不是數字
-- 嘗試由運算元取得處理程式。
local h =
metatable(op).__unm
if h then-- 用運算元呼叫處理程式
return (h(op))
else --
沒有可用的處理程式:預設行為
error(...)
end
end
end
"concat": .. (連線)操作。
function
concat_event (op1, op2)
if (type(op1) == "string" or type(op1) ==
"number") and
(type(op2) == "string" or type(op2) == "number")
then
return op1 .. op2 -- 字串連線原語
else
local h =
getbinhandler(op1, op2, "__concat")
if h then
return
(h(op1, op2))
else
error(...)
end
end
e
nd
"len": # 操作。
function
len_event (o
p)
if type(op) == "string" then
return
strlen(op) -- 取字串長度原語
elseif type(op) == "table" then
return #op --
取表長度原語
else
local h = metatable(op
).__len
if h then
-- 用運算元呼叫處理程式
return (h(op))
else -- 沒有可用的處理程式:預設行為
error(...)
end
end
e
nd
"eq": == 操作。
函式getcomphandler定義Lua如何選擇比較操作符的元方法。只有待比較的兩個物件型別和選定操作對應的元方法都相同,才會選擇該元方法。
function
getcomphandler (op1, op2, event)
if type(op1) ~= type(op2) then
return nil end
local mm1 = metatable(op1)[event]
local mm2 =
metatable(op2)[event]
if mm1 == mm2 then
return mm1
else
return nil
end
end
"eq"
事件定義如下:
function
eq_event (op1, op2)
if type(op1) ~= type(op2) then -- 型別不同?
return false
-- 物件不同
end
if op1 == op2 then -- 相等原語?
return true
-- 物件相同
end -- 嘗試元方法
local h =
getcomphandler(op1, op2, "__eq")
if h then
return (h(op1,
op2))
else
return false
end
end
a
~= b等價於not (a == b)。
"lt": < 操作。
function
lt_event (op1, op2)
if type(op1) == "number" and type(op2) ==
"number" then
return op1 < op2 -- 數字比較
elseif
type(op1) == "string" and type(op2) == "string" then
return op1 <
op2 -- 詞典順序比較
else
local h = getcomphandler(op1, op2,
"__lt")
if h then
return (h(op1, op2))
else
error(...);
end
end
end
a
> b等價於b < a。
"le": <= 操作。
function
le_event (op1, op2)
if type(op1) == "number" and type(op2) ==
"number" then
return op1 <= op2 -- 數字比較
elseif
type(op1) == "string" and type(op2) == "string" then
return op1
<= op2 -- 詞典順序比較
else
local h = getcomphandler(op1,
op2, "__le")
if h then
return (h(op1, op2))
else
h =
getcomphandler(op1, op2, "__lt")
if h then
return not
h(op2, op1)
else
error(...);
end
end
end
end
a
>= b等價於 b <= a。注意,假定a <= b等價於not (b <
a),那麼當沒有“le”元方法時,Lua嘗試“lt”。
"index": 索引訪問table[key]。
function
gettable_event (table, key)
local h
if type(table)
== "table" then
local v = rawget(table, key)
if v ~= nil
then
return v
end
h = metatable(table).__index
if h == nil
then
return nil
end
else
h =
metatable(table).__index
if h == nil then
error(...);
end
end
if type(h) ==
"function" then
return (h(table, key)) -- 呼叫處理程式
else
return
h[key] -- 對它重複上述操作
end
end
"newindex": 索引賦值table[key]
= value。
function settable_event (table, key, value)
local h
if type(table)
== "table" then
local v = rawget(table, key)
if v ~= nil
then
rawset(table, key, value);
return
end
h =
metatable(table).__newindex
if h == nil then
rawset(table,
key, value);
return
end
else
h =
metatable(table).__newindex
if h == nil then
error(...);
end
end
if type(h) ==
"function" then
h(table, key,value) -- 呼叫處理程式
else
h[key] =
value -- 對它重複上述操作
end
e
nd
"call": 當Lua呼叫值時被呼叫。
function
function_event (func, ...)
if type(func) == "function" then
return
func(...) -- 呼叫原語
else
local h =
metatable(func).__call
if h then
return h(func, ...)
else
error(...)
end
end
e
nd