1. 程式人生 > >Lua 中的元表和元方法

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