lua中每個(gè)值都可以擁有一個(gè)元表黎泣,元表是一個(gè)普通的lua table仰禀,定義了原始值在特定操作下的行為。
- setmetatable新荤、getmetatable就可以設(shè)置元表和獲取元表:
local t = {}
local mt = {}
setmetatable(t, mt)
getmetatable(t)
- 改變?cè)碇刑囟ǖ膋ey凡橱,來改變?cè)贾档膶?duì)應(yīng)行為小作,這個(gè)key對(duì)應(yīng)的value(table或function)叫”元方法“。
通過例子可以更清楚的理解元表\元方法的含義:
1. __call 元方法
-- 嘗試調(diào)用一個(gè)非函數(shù)類型值
local A = {}
local metaA = {
__call = function(...)
print("call A with args : ", ...)
end
}
setmetatable(A, metaA)
A("string", 1) -- output: call A with args: table:0x2814cd640 string 1
當(dāng)調(diào)用A(A是一個(gè)table)時(shí)稼钩,會(huì)找A的元表中的__call元方法顾稀,A作為第一個(gè)參數(shù),調(diào)用A的參數(shù)列表隨后坝撑。
也可以用”:“定義__call元方法静秆,傳入的第一個(gè)參數(shù)A 則就是self :
-- 嘗試調(diào)用一個(gè)非函數(shù)類型值
local A = {}
local metaA = {}
function metaA:__call(...)
print("call A with self : ", self)
print("with args : ", ...)
end
setmetatable(A, metaA)
A("string", 1)
-- output: call A with self: table:0x2814cd640
-- with args: string 1
tips:
(1) __call元方法可以用來實(shí)現(xiàn)類似構(gòu)造方法的調(diào)用形式粮揉。
(2) 在調(diào)用某個(gè)值a前不確定它是否是function時(shí),除了判斷 type(a) == "function" 外抚笔,還有可能a不是function 但a的元表__call是function扶认,也是可以成功調(diào)用的。
2. __index 元方法
-- 嘗試訪問一個(gè)不存在的key
local A = {}
print( A.a ) -- output: nil
-- 1.__index是一個(gè)table
local metaA = {
__index = {
a = "123"
}
}
setmetatable(A, metaA)
print( A.a )
-- output: 123
-- 2.__index是一個(gè)function
local metaA2 = {
__index = function(t, k)
print("try to get key : "..k.." from : ",t)
end
}
setmetatable(A, metaA2)
print( "A.a is : ", A.a )
-- output: try to get key : a from table: 0x281495640
-- A.a is : nil (這里由于__index并沒有返回任何值殊橙,所以打印 nil )
lua中查找表元素的過程:
1.在表中查找辐宾,如果找到,返回該元素膨蛮,找不到則繼續(xù)
2.判斷該表是否有元表叠纹,如果沒有元表,返回 nil敞葛,有元表則繼續(xù)誉察。
3.判斷元表有沒有 __index 方法,如果 __index 方法為 nil制肮,則返回 nil冒窍;如果 __index 方法是一個(gè)表,則重復(fù) 1豺鼻、2、3款慨;如果 __index 方法是一個(gè)函數(shù)儒飒,則返回該函數(shù)的返回值(傳入查找的表和key)。
3. __newindex 元方法
__newindex 元方法用來對(duì)表更新檩奠,當(dāng)你給表的一個(gè)不存在的key賦值桩了,解釋器就會(huì)查找__newindex 元方法:
-- 嘗試給一個(gè)不存在的 key 賦值
local A = {}
A.a = "123"
print("A.a is ", A.a)
-- output: A.a is 123
local metaA = {
__newindex = function(t, k, v)
print("try to set value:", v, " for key:", k, " for ", t)
end
}
setmetatable(A, metaA)
A.a = "456"
print("A.a is ", A.a)
-- output: A.a is "456"
-- 此時(shí) A 已經(jīng)包含 key "a", 所以直接改變 A.a 而沒有走元表的__newindex
A.a = nil
A.a = "789"
print("A.a is ", A.a)
-- output: try to set value: 789 for key: a for table: 0x2814aec00
-- A.a is nil
-- 這里由于已經(jīng)將 A.a 置空,再次賦值時(shí)沒有這個(gè) key 所以走了元表的__newindex埠戳,元方法只進(jìn)行了打印并沒有任何操作井誉,所以打印 A.a 仍是nil
給table的某個(gè)key賦值時(shí),只有當(dāng)表中不存在這個(gè)key時(shí)才會(huì)找元表的__newindex方法整胃,傳入表t颗圣、要賦值的key和value,當(dāng)表中已經(jīng)存在這個(gè)key時(shí)則會(huì)直接賦值屁使。
利用__index和__newindex可以實(shí)現(xiàn)一些有意思的功能在岂,比如:
- 實(shí)現(xiàn)類似switch/case中的default語句:
local Score = setmetatable({
["A"] = "91~100",
["B"] = "81~90",
["C"] = "61~80",
["D"] = "0~60"
}, {
__index = function()
return "error";
end
})
print(Score.A) -- "91~100"
print(Score.E) -- "error"
- 將一個(gè)表作為另一個(gè)表的元表的__index,從而實(shí)現(xiàn)表繼承關(guān)系:Lua實(shí)現(xiàn)繼承蛮寂;
- 將__index 和 __newindex作為get蔽午、set方法:Lua實(shí)現(xiàn)KVO
4. 運(yùn)算符方法
運(yùn)算符方法用來定義table的運(yùn)算操作,類似C++中的運(yùn)算符重載
local A = {
a = "a"
}
local B = {
a = "b"
}
local m = {
__add = function(t1, t2)
return {
a = t1.a .. t2.a
}
end
}
setmetatable(A, m)
setmetatable(B, m)
print((A + B).a) -- output: ab
print((B + A + B).a) -- output: bab
Lua中所有的運(yùn)算符元方法:https://www.lua.org/manual/5.3/manual.html#2.4
5. 垃圾回收方法
local A = {}
local metaA = {
__gc = function(...)
print("receive gc with args : ", ...)
end
}
setmetatable(A, metaA)
collectgarbage()
ps. lua5.2以上版本
6. 弱引用表
弱引用表允許它的key和value被gc回收酬蹋,通過設(shè)置元表的__mode實(shí)現(xiàn)及老,__mode是一個(gè)字符串抽莱,如果包含"k"則key是弱引用,如果包含"v"則value是弱引用骄恶。
local k = {}
local v = {}
a = {}
a[k] = v
k = nil
v = nil
collectgarbage()
for i, v in pairs(a) do
print(i, v)
end
-- output: table: 0x282314a40 table: 0x282314840
setmetatable(a, {__mode = "k"})
collectgarbage()
print("weak table:")
for i, v in pairs(a) do
print(i, v)
end
-- output: weak table: