lua元表以及元方法
轉載:
https://www.cnblogs.com/Dong-Forward/p/6063365.html
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"
"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的記憶體位置。比如說上面的。
"pairs":迭代器的元方法,在執行pairs(t)的時候,會先找表t的元方法“__pairs”,如果有就以t為引數呼叫他,如果沒有,就返回三個值next函式, t已經nil。