lua3 Metatable(元表)


本文來自: 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
相關文章
相關標籤/搜索