lua的任何值都可以設(shè)置元表畦徘,其中table用 setmetatable, 其它類型使用debug.setmetatable.
通過(guò)這個(gè)阐肤,可以獲得跟cpp operator重載一樣的騷操作。比如浑塞,可以給一個(gè)number 設(shè)置一個(gè)元表借跪,實(shí)現(xiàn)__add方法,但是__add中用減法酌壕,這樣掏愁,當(dāng)一個(gè)number被設(shè)置元表后,進(jìn)行加法運(yùn)算卵牍,可以得到減法的結(jié)果果港。
元表的key分類
- 運(yùn)算
__add,__div,__idiv,__mul,__sub,__pow,__unm,__mod - 二進(jìn)制運(yùn)算
__band,__bnot,__bor,__bxor,__shl,__shr - 比較
__eq,__lt,__le - 字符串相關(guān)
__name,__tostring,__concat - 表操作
__len,__index,__newindex,__pairs - 其它
__call,__close,__gc,__mode,__metatable
詳細(xì)解釋用法
通用類
運(yùn)算/二進(jìn)制運(yùn)算/比較/__concat
這幾個(gè)都是比較簡(jiǎn)單的,函數(shù)類似:function(lvalue,rvalue) return xxx end 這樣的
字符串相關(guān)
__name
- 下面是一個(gè)簡(jiǎn)單的例子
local t = {id=1}
local mt = {
__name="newTypeName",
}
setmetatable(t,mt)
print(type(t),t) --輸出 table newTypeName: 0x55a032ead330
- 但是更經(jīng)常的是在c中使用
int luaL_newmetatable (lua_State *L, const char *tname);
這時(shí)會(huì)在 registry 創(chuàng)建一個(gè){__name=tname}的表糊昙,并在 registry[tname]=new table. 并將這個(gè)new table TODO 這里要看看是不是
__tostring
print(t)的時(shí)候辛掠,直接打印出來(lái)
local t = {id=1}
local mt = {
__tostring=function(_t) return "id=".._t.id end
}
setmetatable(t,mt)
print(type(t),t)--table id=1
表操作
__len
- 對(duì)非string/table調(diào)用時(shí)會(huì)查看有沒有__len這個(gè)函數(shù),如果沒有就報(bào)錯(cuò)
__index,__newindex
__index為 t[xxx] 獲取操作释牺,當(dāng)不存在時(shí)調(diào)用函數(shù)或獲取table
__newindex為 t[xxx]=value 設(shè)置操作萝衩,當(dāng)不存在key時(shí)調(diào)用函數(shù)
__pairs
- 可以重定義table的pairs ,如果是其它類型没咙,也可以定義猩谊。__pairs對(duì)應(yīng)的函數(shù)需要返回 function,傳入的參數(shù)1,nil,
簡(jiǎn)單例子如下
local function testxx()
local s = "a string"
local mt = {
__pairs = function(_s)
return function(_ts,_idx)
local len = #_ts
_idx = _idx or 0
_idx = _idx + 1
if _idx <= len then
return _idx,string.sub(_ts,_idx,_idx)--這里先return idx
end
return nil,nil
end,_s,nil
end
}
debug.setmetatable(s,mt)
for k,v in pairs(s) do
print(k,v)
end
end
testxx()
輸出如下
其它
__call
使非function能像函數(shù)一樣調(diào)用
local t = {id=2}
setmetatable(t,{
__call = function(a,arg1)
return a.id+arg1
end
})
print(t(10))
--輸出
--12
__close
- 在lua5.4中,基于 __gc的調(diào)用時(shí)機(jī)不明確镜撩,__close是專門用于一個(gè)變量逃出作用域時(shí)被調(diào)用的函數(shù)预柒。正常退出及 break/goto/return 及 拋出錯(cuò)誤時(shí),都會(huì)被調(diào)用袁梗∫搜欤可以看作是golang的defer ,c#的using語(yǔ)句,使用例子如下
local function createXX()
local ret = {}
local mt = {__close=function(t,errObj)
print("close to-be-closed variable")
end}
setmetatable(ret,mt)
return ret
end
do
local x<close> = createXX()
end
- 其它特性:
2.1. 順序跟__gc的一樣,是倒序
2.2. 如果__close函數(shù)拋出異常遮怜,則會(huì)被外面捕獲
local function createXX()
local ret = {}
local mt = {__close=function(t,errObj)
print("close to-be-closed variable",t.id)
assert(false,"hhhh")
end}
setmetatable(ret,mt)
return ret
end
do
local x<close> = createXX()
x.id = 2
local a<close> = createXX()
a.id = 3
end
輸出如下
2.3 如果一個(gè)coroutine 被 yield 而且從來(lái)沒有被resume淋袖,則不會(huì)被close
local function testCoroutineClose()
local function createV()
local ret = {}
local mt = {__close=function(t,errObj)
print("close",t.id)
end}
setmetatable(ret,mt)
return ret
end
local co = coroutine.create(function()
do
local a1<close> = createV()
a1.id = 1
end
coroutine.yield(1,2)
do
local a1<close> = createV()
a1.id = 2 --因?yàn)闆]有resume,所以不會(huì)調(diào)用close
end
return 3,4
end)
coroutine.resume(co)
--coroutine.resume(co)
end
testCoroutineClose()
--輸出
--close 1
2.4. 如果一個(gè)coroutine以error結(jié)束锯梁,則因?yàn)闆]有清空stack即碗,所以也不會(huì) close 任何變量 這時(shí)需要 __gc 或 coroutine.close來(lái)實(shí)現(xiàn)回收。當(dāng)然陌凳,如果coroutine 是用 coroutine.wrap來(lái)創(chuàng)建剥懒,那么即使是錯(cuò)誤也會(huì)有close被調(diào)用
local function testCoroutineClose()
local function createV()
local ret = {}
local mt = {__close=function(t,errObj)
print("close",t.id)
end}
setmetatable(ret,mt)
return ret
end
local co = coroutine.create(function()
do
local a1<close> = createV()
a1.id = 1
end
local a<close> = createV()
a.id=2
coroutine.yield(1,2)
do
local a1<close> = createV()
a1.id = 3
end
return 3,4
end)
coroutine.resume(co)
coroutine.close(co)
end
testCoroutineClose()
--輸出
--close 1
--close 2
__gc
- 總述 當(dāng)一個(gè)object( table/userdata) 在被gc認(rèn)為要回收的時(shí)候會(huì)被調(diào)用,因?yàn)槔厥諘?huì)發(fā)生在進(jìn)程的任意階段合敦,所以__gc也可能在進(jìn)程的任意階段被調(diào)用初橘,在lua_close時(shí),肯定會(huì)調(diào)用,常用于管理資源比如文件,網(wǎng)絡(luò),數(shù)據(jù)庫(kù)鏈接,或lightuserdata.
- __gc這個(gè)值需要在 setmatatable 函數(shù)調(diào)用之前就已經(jīng)有這個(gè)key了,如果先調(diào)用setmetatable保檐,后再將mt加上__gc這個(gè)key對(duì)應(yīng)的函數(shù)耕蝉,那么是無(wú)效的
local function test1()
local t = {}
local mt = {}
setmetatable(t,mt)
mt.__gc = function(t)
print("this line not print")
end
end
test1()
- 當(dāng)垃圾回收認(rèn)為一個(gè)object需要回收的時(shí)候,不會(huì)馬上對(duì)內(nèi)存進(jìn)行回收夜只,而是先把這個(gè)object放到一個(gè)list上去垒在,然后再將從list中逐一檢查有沒有 __gc這個(gè)函數(shù),如果有扔亥,就調(diào)用场躯。 另list好像是從尾插向頭的,第一個(gè)調(diào)用__gc的object是最后
local tdmt = {
__gc = function(t)
print(t.id)
end
}
local function topdowntest1()
for i=1,3,1 do
setmetatable({id=i},tdmt)
end
collectgarbage("collect")
end
topdowntest1()
-- 輸出
--3
--2
--1
- 因?yàn)樵?垃圾回收的cycle中砸王,需要使用這個(gè)object, 所以對(duì)內(nèi)存回收是在下一個(gè) 垃圾回收 cycle.所以如果你在__gc這個(gè)函數(shù)中推盛,把這個(gè)t又引用到別的地方去(比如global) 那么峦阁,它就不會(huì)被回收谦铃,但是當(dāng) __gc這個(gè)被調(diào)用之后,不會(huì)再被調(diào)用.
local tdmt = {
__gc = function(t)
print(t.id)
end
}
local sgmt = {
__gc = function(t)
gv = t
print("global",t.id)
setmetatable(gv,tdmt)
end
}
local function resurrected()
setmetatable({id=4},tdmt)
setmetatable({id=5},sgmt)
collectgarbage("collect")
os.execute("sleep 1")
end
resurrected()
//輸出
--global 5
--4
--5
- 在__gc函數(shù)中榔昔,除了不能yield之外驹闰,啥都可以,例如:報(bào)錯(cuò)撒会,創(chuàng)object嘹朗,甚至顯示調(diào)用gc. 報(bào)錯(cuò)之后,并不會(huì)傳播出來(lái)诵肛。只是當(dāng)前函數(shù)在報(bào)錯(cuò)之后不會(huì)再執(zhí)行屹培。
local sgmt = {
__gc = function(t)
gv = t
print("global",t.id)
--assert(false,"xxxx") 如果這句不注釋,則 下面的那句不會(huì)執(zhí)行
setmetatable(gv,tdmt)
assert(false,"xxxx") --不會(huì)有任何報(bào)錯(cuò)信息出現(xiàn)
end
}
local function resurrected()
setmetatable({id=5},sgmt)
collectgarbage("collect")
os.execute("sleep 1")
end
resurrected()
__mode
被kv修飾過(guò)的怔檩,引用了跟沒引用一樣
local function testmode()
local t = {}
setmetatable(t,{__mode="kv"})
t[1] = {}
t[2] = {}
collectgarbage("collect")
print(next(t)) --打印出nil
---
local c={}
c[1] = {}
t[1] = c[1]
collectgarbage("collect")
print(next(t)) --有值
c[1] = nil
collectgarbage("collect")
print(next(t))--打印出nil
end
testmode()
- 如果k/v 任一weak 只要其中一個(gè)被回收褪秀,如果另一個(gè)只能過(guò)這個(gè)t 來(lái)獲取,事實(shí)上你也取不到另一個(gè)薛训。就是說(shuō)媒吗,如果 key是weak ,但value是strong乙埃,那么如果key被回收闸英,就算value沒有被回收,你也獲取不到value 介袜。
- 如果是中間過(guò)程中轉(zhuǎn)變weak為strong,也有可能有一些已經(jīng)被回收(因?yàn)檫@個(gè)改動(dòng)甫何,需要下一個(gè)回收cycle才生效)
- 只有會(huì)被gc的value才會(huì)被weak引用刪除。比如table userdata遇伞。string也是會(huì)被gc的辙喂,但是string在這又不會(huì)被weak引用刪除的
local function testmode()
local t = {}
setmetatable(t,{__mode="kv"})
t[1] = {}
t[2] = {}
collectgarbage("collect")
print(next(t))
local c={}
c[1] = {}
t["abc"] = c[1]
collectgarbage("collect")
print(next(t))--有值
c[1] = nil
collectgarbage("collect")
print(next(t))
t["cba"] = "aaaa"
collectgarbage("collect")
print(next(t))--有值
end
testmode()
__metatable
都不知道有啥用,不能直接獲取/賦值