lua元表以及元方法
lua中的變量是沒有數據類型的,值有類型。類型有八種nil,number,boolean, string, function, thread, userdata以及table。
Lua 中的每個值都可以有一個 元表 。 這個 元表 就是一個普通的 Lua 表,它用于定義原始值在特定操作下的行為。例如,當你對非數字值做加操作時, Lua 會檢查該值的元表中的 "__add" 域下的函數。 如果能找到,Lua 則調用這個函數來完成加這個操作。
元表中的鍵對應著不同的 事件 名; 鍵關聯的那些值被稱為 元方法 。 在上面那個例子中引用的事件為 "add" , 完成加操作的那個函數就是元方法。
可以用 getmetatable 函數 來獲取任何值的元表。
使用 setmetatable 來替換一張表的元表。在 Lua 中,你不可以改變表以外其它類型的值的元表 (除非你使用調試庫); 若想改變這些非表類型的值的元表,請使用 C API.
接下來會給出一張元表可以控制的事件的完整列表。 每個操作都用對應的事件名來區分。 每個事件的鍵名用加有 '__' 前綴的字符串來表示; 例如:"add" 操作的鍵名為字符串 "__add"。
"add": + 操作。 如果任何不是數字的值(包括不能轉換為數字的字符串)做加法, Lua 就會嘗試調用元方法。 首先、Lua 檢查第一個操作數(即使它是合法的), 如果這個操作數沒有為 "__add" 事件定義元方法, Lua 就會接著檢查第二個操作數。 一旦 Lua 找到了元方法, 它將把兩個操作數作為參數傳入元方法, 元方法的結果(調整為單個值)作為這個操作的結果。 如果找不到元方法,將拋出一個錯誤。
"sub": - 操作。 行為和 "add" 操作類似。
"mul": * 操作。 行為和 "add" 操作類似。
"div": / 操作。 行為和 "add" 操作類似。
"mod": % 操作。 行為和 "add" 操作類似。
"pow": ^ (次方)操作。 行為和 "add" 操作類似。
"unm": - (取負)操作。 行為和 "add" 操作類似。
"idiv": // (向下取整除法)操作。 行為和 "add" 操作類似。
"band": & (按位與)操作。 行為和 "add" 操作類似, 不同的是 Lua會在任何一個操作數無法轉換為整數時嘗試取元方法。
"bor": | (按位或)操作。 行為和 "band" 操作類似。
"bxor": ~ (按位異或)操作。 行為和 "band" 操作類似
"bnot": ~ (按位非)操作。 行為和 "band" 操作類似。
"shl": << (左移)操作。 行為和 "band" 操作類似。
"shr": >> (右移)操作。 行為和 "band" 操作類似。
"concat": .. (連接)操作。 行為和 "add" 操作類似, 不同的是 Lua在任何操作數即不是一個字符串 也不是數字(數字總能轉換為對應的字符串)的情況下嘗試元方法。
"len": # (取長度)操作。 如果對象不是字符串,Lua 會嘗試它的元方法。 如果有元方法,則調用它并將對象以參數形式傳入, 而返回值(被調整為單個)則作為結果。 如果對象是一張表且沒有元方法,Lua 使用表的取長度操作。 其它情況,均拋出錯誤。
"eq": == (等于)操作。 和 "add" 操作行為類似, 不同的是 Lua 僅在兩個值都是表或都是完全用戶數據 且它們不是同一個對象時才嘗試元方法。 調用的結果總會被轉換為布爾量。
"lt": < (小于)操作。 和 "add" 操作行為類似, 不同的是 Lua 僅在兩個值不全為整數也不全為字符串時才嘗試元方法。 調用的結果總會被轉換為布爾量。
"le": <= (小于等于)操作。 和其它操作不同, 小于等于操作可能用到兩個不同的事件。 首先,像 "lt" 操作的行為那樣,Lua 在兩個操作數中查找 "__le" 元方法。 如果一個元方法都找不到,就會再次查找 "__lt" 事件, 它會假設 a <= b 等價于 not (b < a)。 而其它比較操作符類似,其結果會被轉換為布爾量。
上面的這些元方法原理上都可以歸為一類,我們舉個例子來看:
local mt = {}
mt.__add = function(a,b)
return a.v + b.v/2
end
local a = {v = 10}
local b = {v = 12}
setmetatable(a, mt)
print("a + b :", a + b)
運行結果如下:
相當于重載了“+”號。
"index": 索引 table[key]。 當 table 不是表或是表 table 中不存在key 這個鍵時,這個事件被觸發。 此時,會讀出 table 相應的元方法。盡管名字取成這樣, 這個事件的元方法其實可以是一個函數也可以是一張表。 如果它是一個函數,則以 table 和 key 作為參數調用它。如果它是一張表,最終的結果就是以 key 取索引這張表的結果。(這個索引過程是走常規的流程,而不是直接索引, 所以這次索引有可能引發另一次元方法。
"newindex": 索引賦值 table[key] = value 。 和索引事件類似,它發生在 table 不是表或是表 table 中不存在 key 這個鍵的時候。 此時,會讀出 table 相應的元方法。同索引過程那樣, 這個事件的元方法即可以是函數,也可以是一張表。 如果是一個函數, 則以 table、 key、以及 value 為參數傳入。如果是一張表, Lua 對這張表做索引賦值操作。 (這個索引過程是走常規的流程,而不是直接索引賦值, 所以這次索引賦值有可能引發另一次元方法。)一旦有了 "newindex" 元方法, Lua 就不再做最初的賦值操作。(如果有必要,在元方法內部可以調用 rawset 來做賦值。
通過一個例子來說明其原理:
local mt = {}
mt.__index = function(t, k)
print("call index function")
end
mt.__newindex = function(t,k,v)
print("call new index function")
end
local t = {}
setmetatable(t, mt)
local temp = t.a
t.a = 10
輸出如下:
也就是說, 當調用表"t"中的元素"a",卻在表"t"中找不到"a"的時候,會把“t”,“a”做為參數來調用“__index”函數。
當賦值表"t"中的元素"a",卻在表"t"中找不到"a"的時候,會把“t”,“a”,value做為參數來調用“__newindex”函數。
"call": 函數調用操作 func(args)。 當 Lua 嘗試調用一個非函數的值的時候會觸發這個事件 (即 func 不是一個函數)。 查找 func 的元方法, 如果找得到,就調用這個元方法, func 作為第一個參數傳入,原來調用的參數(args)后依次排在后面。
通過一個例子來說明其原理:
local mt = {}
mt.__call = function(f,...)
print("call table like a function", f ,...)
end
local temp = {}
setmetatable(temp, mt)
temp(1,2,3)
輸出如下:
相當于把函數調用賦予到了一個非函數類型。
這個元方法比較實用。可以把事件改變成表,可以事先賦予事件上值。還可以當成一個類的構造函數實使用。
此外還有一些元方法,書中并沒有歸類到這些事件里面。我做了一些統計
"mode":弱表屬性,賦予一張表弱引用屬性。(弱表比較有趣,下一篇文章會做說明)
"gc":在對象被GC的時候,會先調用元表里面的“gc”域。
"tostring":當調用tostring(obj)的時候,會先查找obj的元方法中的__tostring,如果有就調用,沒有就會打印obj的內存位置。比如說上面的 。
來自:http://www.cnblogs.com/Dong-Forward/p/6063365.html