Lua元表與元方法介紹
一、基本介紹:
1.Lua中的每個值都可以有一個metatable,這個 metatable 就是一個原始的 Lua table (metatable 中的鍵名為 事件 (event) ,把其中的值叫作 元方法 (metamethod))
2.getmetatable函式來查詢到任何一個值的 metatable
3.setmetatable 函式來替換掉 table 的 metatable
4.在Lua程式碼中只能設定table的元表,其它型別值的元表只能在C程式碼中設定
5.每個 table 和 userdata 擁有獨立的 metatable (當然多個 table 和 userdata 可以共享一個相同的表作它們的 metatable); 其它所有型別的值,每種型別都分別共享唯一的一個 metatable
6.Lua在建立新table時預設不會建立元表
7.一個table也可以作為自己的元表
8.一個 metatable 可以控制一個物件做數學運算操作、比較操作、連線操作、取長度操作、取下標操作時的行為, metatable 中還可以定義一個函式,讓 userdata 作垃圾收集時呼叫它
二、metatable 可以控制的操作
1.算術類元方法
__add(加法"+") __sub(減法"-") __mul(乘法"*") __div(除法"/") __unm(相反數"-") __mod(取模"%") __pow(乘冪"^") __concat(連線"..") __len(求長度"#")
先找第一個值的元方法,如果沒有再找第二個值的元方法,都沒有報錯
add:+操作例項:
local tableA = {1,3,"abc"}; local tableB = {5,7}; local mt = {}; mt.__add = function(t1,t2) for _,item in ipairs(t2) do table.insert(t1,item); end return t1; end setmetatable(tableA,mt); setmetatable(tableB,mt); local tableSum = tableA + tableB; for k,v in ipairs(tableSum) do print("k = " .. k .. " -- v = " .. v); end
2.關係類元方法
__eq(等於) __lt(小於) __le(小於等於)
沒有大於和不等於元方法,但可以轉化實現
a~=b轉化為not(a==b)
a>b轉化為b<a
a>=b轉化為b<=a
eq: == 操作例項。僅僅在參於比較的兩個物件型別相同且有對應操作相同的元方法時才起效。
local tableA = {1,3,"abc"}; local tableB = {5,7}; mt.__eq = function(t1,t2) print("eq"); return true; end setmetatable(tableA,mt); setmetatable(tableB,mt); print("eq = " .. tostring(tableA == tableB));
3.庫定義元方法
__tostring(字串轉換)
tostring函式會用此元方法進行轉換
__metatable(指向元方法)
setmetatable、getmetatable會訪問這個元方法
如果設定成其它內容就可以起到保護元表的功能
__mode(弱引用table模式)
它的值是一個字串
如果包含"k"則表示table裡的key是弱引用模式
如果包含"v"則表示table裡的value是弱引用模式
tostring: 字串化例項
local tableA = {1,3,"abc"};
mt.__tostring = function(t)
return "__tostring" -- 必須返回一個字串
end
setmetatable(tableA,mt);
print(tableA)
4.table訪問的元方法
__index(訪問表中不存在的欄位)
(1)當沒有這個元方法時訪問不存在欄位會返回nil
(2)當有元方法時兩種訪問形式
作為函式時有兩個引數,第一個是被訪問的table,第二個是不存在的key
作為table時就從這個table裡找被訪問的table裡不存在的這個key
(3)通常用於實現繼承特性
(4)作為函式的時候開銷會大一些,但更靈活,可以實現多重繼承和快取等功能
(5)如果不想涉及元方法,可以使用rawget(t,i)"原始訪問",不會加速程式碼執行
__newindex(給表中不存在的欄位賦值)
(1)當沒有這個元方法時會在被訪問的table裡建立新欄位並賦值
(2)當有元方法時兩種訪問形式
作為函式時有三個引數,第一個是被訪問的table,第二個是不存在的key,第三個是value
作為table時,會在這個table裡賦值而不是在被訪問table裡賦值
(3)可以使用rawset(t,k,v)繞過元方法賦值
可以利用這兩個元方法實現很多table的特殊功能
(1)具有預設值的table,把帶有值的table作為__index元方法
(2)跟蹤table的訪問
t = {} --原table
local _t = t --私有化訪問
t = {} --建立程式碼,名字相同
mt = {}
mt.__index = function(t,k)
print("access "..tostring(k))
return _t[k] --訪問原來的table
end
mt.__newindex = function(t,k,v)
print("update "..tostring(k).." to "..tostring(v))
_t[k] = v --更新原來的table
end
setmetatable(t, mt)
但這個例無法遍歷原來的table,pairs只能操作代理table(3)只讀table,__index指向被訪問table,__newindex彈錯