Lua的table是個很有意思的東西癞松。有些內(nèi)容平時寫代碼的時候很少接觸到,但是了解一下還是很有意思的入蛆。
這篇blog參考MetatableEvents响蓉,一個一個邊寫測試邊細(xì)說。
__newindex
原文翻譯:
__newindex
用于分配屬性哨毁,當(dāng)調(diào)用 myTable[key]=value
時枫甲,如果元表中有__newindex
并且指向一個function,就會調(diào)用這個function,傳入的參數(shù)為table, key 和 value
- 用
rawset(myTable, key, value)
可以跳過這個元方法直接給myTable的key屬性賦值為value想幻。 - 如果
__newindex
指向的方法中粱栖,沒有調(diào)用rawset
方法,傳入的鍵值對(key/value)就不會添加到myTable中脏毯。
測試代碼:
local meta = {
__newindex = function(t, key, value)
print("call __newindex",t, key, value)
end
}
local test = {}
setmetatable(test, meta)
print("test", test)
print("meta", meta)
test.name = "t1"
test.name = "t2"
print("test.name", test.name)
---- result output ----
test table: 0x7f9c13406f00
meta table: 0x7f9c13407240
call __newindex table: 0x7f9c13406f00 name t1
call __newindex table: 0x7f9c13406f00 name t2
test.name nil
測試代碼中闹究,當(dāng)給t的name的賦值時,就會觸發(fā)元表中的__newindex指向的function食店,打印的信息可以看到key和value的值渣淤。
__newindex
方法中傳進(jìn)來的參數(shù)t
的指針和test
的指針指向同一個地址,說明__newindex
中的參數(shù)t
吉嫩,并不是元表砂代。
測試代碼中對t.name連續(xù)賦值時,__newindex
會連續(xù)調(diào)用率挣,需要留意一下這里,后面的測試會跟這里做一個對比露戒。
賦值之后打印 t.name 的值是空的椒功。原因是__newindex
并沒有給t.name賦值,我們用一個錯誤的方式給t.name賦值智什,來加深__newindex
的理解动漾。修改一下測試代碼:
local meta = {
__newindex = function(t, key, value)
print("call __newindex",t, key, value)
t[key] = value
end
}
local test = {}
setmetatable(test, meta)
print("test", test)
print("meta", meta)
test.name = "t1"
test.name = "t2"
print(test.name)
---- result output ----
...
lua: C stack overflow
...
報錯信息,棧溢出荠锭。因為t[key] = value
這段代碼會調(diào)用t元表中的__newindex
的方法旱眯,__newindex
的方法又會調(diào)用t[key] = value
,這樣就進(jìn)入了死循環(huán)证九,導(dǎo)致棧溢出删豺。這時就需要用到方法rawset
。
修改測試代碼:
local meta = {
__newindex = function(t, key, value)
print("call __newindex",t, key, value)
rawset(t, key, value)
end
}
local test = {}
setmetatable(test, meta)
test.name = "t1"
test.name = "t2"
print("test.name", test.name)
---- result output ----
call __newindex table: 0x7fdade404e20 name t1
test.name t2
這段代碼中信息比較多
在__newindex
中使用了rawset
方法愧怜,可以看到呀页,沒有棧溢出的錯誤了,說明用rawset
給table賦值拥坛,不會進(jìn)入__newindex
的方法蓬蝶。
給t.name連續(xù)賦值,會發(fā)現(xiàn)只進(jìn)入__newindex
一次猜惋,跟之前不同的是丸氛,我們在__newindex
給t.name賦了值。如果t中沒有這個key時著摔,才會進(jìn)入__newindex
方法缓窜。否則不會進(jìn)入。
__newindex
的默認(rèn)值就是上面meta.__newindex
的代碼。如果不需要額外處理雹洗,完全可以不寫香罐。如下:
local meta = {}
local test = {}
setmetatable(test, meta)
test.name = "t1"
print("test.name", test.name)
---- result output ----
test.name t1
__index
翻譯原文
__index
用于控制屬性(prototype)的繼承忆蚀,當(dāng)訪問 myTable[key] 時饮笛,如果myTable中不存在這個key,但是如果元表(metatable)中有 __index
時:
- 如果
__index
是一個function
搀菩,傳遞的參數(shù)是table
和key
,function
的返回值作為結(jié)果返回螃成。 - 如果
__index
是一個table
旦签,就返回這個表中key對應(yīng)的值。- 如果這個
table
不存在該key
寸宏,但是這個table
有元表宁炫,會繼續(xù)尋找元表中的__index
屬性,以此類推氮凝。都沒有就返回nil
- 如果這個
- 使用 "rawget(myTable,key)" 可以跳過這個元方法(__index).
寫點測試:
local test = {}
local meta = {
__index = function(t, k)
print("__index", k)
if rawget(t, k) == nil then
print("Can't find ".. k)
end
return rawget(t, k)
end,
}
setmetatable(test, meta)
print("test.name1", test.name)
test.name = "hello"
print("test.name2", test.name)
---- result output ----
__index name
Can't find name
test.name1 nil
test.name2 hello
__newindex
和__index
其實可以類比成setter和getter羔巢,這么類比會比較容易理解,但是實際上還是有比較大的區(qū)別罩阵。
上面的測試中__index
是個function竿秆。當(dāng)執(zhí)行test.name時,如果test.name是nil稿壁,會調(diào)用__index
的function幽钢,并返回function的返回值。否則傅是,直接返回test[key]匪燕,不會進(jìn)入__index
。
再做一個測試喧笔,這次__index
是個table
local test = {}
local meta = {
__index = {name="meta"},
}
setmetatable(test, meta)
print("test.name1", test.name)
test.name = "hello"
print("test.name2", test.name)
---- result output ----
test.name1 meta
test.name2 hello
這個測試可以看到帽驯,訪問順序是先訪問test的name,如果沒有值书闸,再訪問test元表中__index
的table界拦。如果test的元表還有元表,會繼續(xù)向上訪問梗劫,Lua繼承的實現(xiàn)就是利用這個特性享甸。
掌握__newindex
和__index
這兩個元方法,可以把這兩個元方法看做兩個事件梳侨,那要就要清楚兩個方法的觸發(fā)條件和特性蛉威。才能融會貫通。
舉個例子:
禁用全局變量
local meta = {
__newindex = function(t, k, v)
print("Error! Can't set globle variable", k)
end,
-- 默認(rèn)實現(xiàn)
-- __index = function(t, k)
-- return rawget(t, k)
-- end
}
setmetatable(_G, meta)
test = "test"
print(test)
---- result output ----
Error! Can't set globle variable test
nil
__mode
原文翻譯:
控制弱引用走哺,用字符k
和v
來代表table的鍵
和值
是否是弱引用蚯嫌。這個感覺沒什么好說的,只寫個測試就好了。
local meta = {__mode = "k"}
local test = {}
setmetatable(test, meta)
key = {}
test[key] = 1
key = {}
test[key] = 2
for k,v in pairs(test) do
print(v)
end
collectgarbage()
print("collectgarbage")
for k,v in pairs(test) do
print(v)
end
---- result output ----
1
2
collectgarbage
2
例子中當(dāng)調(diào)用collectgarbage()進(jìn)行回收后择示,test表中只剩下一個值束凑。弱引用的key被清理了。我們也可以在__mode中設(shè)置v
,kv
來表示值
鍵和值
都是弱引用栅盲。
__call
原文翻譯:
把table當(dāng)做一個function使用汪诉,當(dāng)table后跟一個圓括號時,而且table的元表中的__call指向一個function谈秫,就會調(diào)用這個function扒寄,table自己做為第一個參數(shù),后面可接任意數(shù)量的參數(shù)拟烫,返回值就是function的返回值该编。
測試代碼來模擬實現(xiàn)一個構(gòu)造方法。
local meta = {
__call = function(t, ...)
local instance = {}
for k, v in pairs(t) do
instance[k] = v
end
return instance
end
}
local A = setmetatable({}, meta)
function A:info()
print("info",self)
end
local a = A()
local b = A()
a:info()
b:info()
---- result output ----
info table: 0x7f8771e05030
info table: 0x7f8771e050a0
__metatable
原文翻譯:
隱藏真正的元表硕淑,當(dāng)調(diào)用getmetatable
時课竣,而且table的元表有__metatable
字段,則返回__metatable
字段中的值置媳。
測試代碼:
local meta = {
name = "meta"
}
local test = setmetatable({}, meta)
print(getmetatable(test).name)
local meta = {
__metatable = {name = "__metatable"},
name = "meta"
}
local test = setmetatable({}, meta)
print(getmetatable(test).name)
---- result output ----
meta
__metatable
結(jié)果很直觀不解釋了稠氮,我另外還做了個的測試,讓__metatable
指向了一個function半开,調(diào)用getmetatable
時也會返回這個function。很有意思赃份,但是暫時沒想到有什么應(yīng)用場景寂拆。
__tostring
原文翻譯:
控制字符串的表現(xiàn),當(dāng)調(diào)用tostring(myTable)
時抓韩,且myTable的元表中有__tostring
字段時纠永,就會調(diào)用這個方法。返回值是方法的返回值谒拴。
測試代碼:
local meta = {
__tostring = function(t)
return string.format("My name is %s", t.name)
end
}
local test = setmetatable({}, meta)
test.name = "test"
print(test)
print(tostring(test))
---- result output ----
My name is test
My name is test
這個也不做過多說明了尝江,很容易理解。有一點提一下就是print方法會自動調(diào)用tostring(test)
__len
原文翻譯:
控制table的長度英上。當(dāng)用#
操作符請求長度時炭序,且table的元表有__len
字段指向一個function,就會調(diào)用這個function苍日,參數(shù)是table自己惭聂,返回值是function的返回值。
寫個測試代碼:
local meta = {
__len = function(t)
local result = 0
for k, v in pairs(t) do
result = result + 1
end
return result
end
}
local test = {
[1] = "A",
[2] = "B",
[3] = "C",
[5] = "D",
[6] = "E",
[8] = "F",
}
print(#test)
setmetatable(test, meta)
print(#test)
---- result output ----
3
6
Lua中使用#
獲取長度有個特性相恃,就是如果某個key對應(yīng)的值是nil就結(jié)束辜纲,上面例子中,test第4個值是nil,那返回的長度為3耕腾。我們重新定義了__len
后返回了见剩,用遍歷的方式計算長度,返回table內(nèi)元素的數(shù)量為6扫俺。
__gc
原文翻譯:
簡單說就是數(shù)據(jù)被垃圾回收的時候會首先觸發(fā)__gc
苍苞。
測試代碼:
local meta = {
__gc = function(t)
print("gc")
end
}
local function test()
local test = {}
setmetatable(test, meta)
end
test()
---- result output ----
gc
Lua table中所有的元方法就分析完了,還有一些操作符重載的牵舵,不細(xì)說了柒啤。