本文來自: http://manual.luaer.cn/ ,2.8章.html
Lua 中的每一個值均可以用一個 metatable。 這個 metatable 就是一個原始的 Lua table , 它用來定義原始值在特定操做下的行爲。 你能夠經過在 metatable 中的特定域設一些值來改變擁有這個 metatable 的值 的指定操做之行爲。 舉例來講,當一個非數字的值做加法操做的時候, Lua 會檢查它的 metatable 中 "__add"
域中的是否有一個函數。 若是有這麼一個函數的話,Lua 調用這個函數來執行一次加法。函數
咱們叫 metatable 中的鍵名爲 事件 (event) ,把其中的值叫做 元方法 (metamethod)。 在上個例子中,事件是 "add"
而元方法就是那個執行加法操做的函數。編碼
你能夠經過 getmetatable
函數來查詢到任何一個值的 metatable。lua
你能夠經過 setmetatable
函數來替換掉 table 的 metatable 。 你不能從 Lua 中改變其它任何類型的值的 metatable (使用 debug 庫例外); 要這樣作的話必須使用 C API 。spa
每一個 table 和 userdata 擁有獨立的 metatable (固然多個 table 和 userdata 能夠共享一個相同的表做它們的 metatable); 其它全部類型的值,每種類型都分別共享惟一的一個 metatable。 所以,全部的數字一塊兒只有一個 metatable ,全部的字符串也是,等等。debug
一個 metatable 能夠控制一個對象作數學運算操做、比較操做、鏈接操做、取長度操做、取下標操做時的行爲, metatable 中還能夠定義一個函數,讓 userdata 做垃圾收集時調用它。 對於這些操做,Lua 都將其關聯上一個被稱做事件的指定健。 當 Lua 須要對一個值發起這些操做中的一個時, 它會去檢查值中 metatable 中是否有對應事件。 若是有的話,鍵名對應的值(元方法)將控制 Lua 怎樣作這個操做。code
metatable 能夠控制的操做已在下面列出來。 每一個操做都用相應的名字區分。 每一個操做的鍵名都是用操做名字加上兩個下劃線 '__
' 前綴的字符串; 舉例來講,"add" 操做的鍵名就是字符串 "__add"
。 這些操做的語義用一個 Lua 函數來描述解釋器如何執行更爲恰當。orm
這裏展現的用 Lua 寫的代碼僅做解說用; 實際的行爲已經硬編碼在解釋器中,其執行效率要遠高於這些模擬代碼。 這些用於描述的的代碼中用到的函數 ( rawget
, tonumber
,等等。) 均可以在 §5.1 中找到。 特別注意,咱們使用這樣一個表達式來從給定對象中提取元方法htm
metatable(obj)[event]
這個應該被解讀做對象
rawget(getmetatable(obj) or {}, event)
這就是說,訪問一個元方法再也不會觸發任何的元方法, 並且訪問一個沒有 metatable 的對象也不會失敗(而只是簡單返回 nil)。
"add": +
操做。
下面這個 getbinhandler
函數定義了 Lua 怎樣選擇一個處理器來做二元操做。 首先,Lua 嘗試第一個操做數。 若是這個東西的類型沒有定義這個操做的處理器,而後 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 math 庫)。
"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 end
"len": #
操做。
function len_event (op) if type(op) == "string" then return strlen(op) -- 原生的取字符串長度 elseif type(op) == "table" then return #op -- 原生的取 table 長度 else local h = metatable(op).__len if h then -- 調用操做數的處理器 return h(op) else -- 沒有處理器:缺省行爲 error(···) end end end
關於 table 的長度參見 §2.5.5 。
"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
。 注意,若是元方法 "le" 沒有提供,Lua 就嘗試 "lt" , 它假定 a <= b
等價於 not (b < a)
。
"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 return h(table, key,value) -- 調用處理器 else h[key] = value -- 或是重複上述操做 end end
"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 end