lua的任何值都可以设置元表,其中table用 setmetatable, 其它类型使用debug.setmetatable.
通过这个,可以获得跟cpp operator重载一样的骚操作。比如,可以给一个number 设置一个元表,实现__add方法,但是__add中用减法,这样,当一个number被设置元表后,进行加法运算,可以得到减法的结果。
元表的key分类
- 运算
__add,__div,__idiv,__mul,__sub,__pow,__unm,__mod - 二进制运算
__band,__bnot,__bor,__bxor,__shl,__shr - 比较
__eq,__lt,__le - 字符串相关
__name,__tostring,__concat - 表操作
__len,__index,__newindex,__pairs - 其它
__call,__close,__gc,__mode,__metatable
详细解释用法
通用类
运算/二进制运算/比较/__concat
这几个都是比较简单的,函数类似:function(lvalue,rvalue) return xxx end 这样的
字符串相关
__name
- 下面是一个简单的例子
local t = {id=1}
local mt = {
__name="newTypeName",
}
setmetatable(t,mt)
print(type(t),t) --输出 table newTypeName: 0x55a032ead330
- 但是更经常的是在c中使用
int luaL_newmetatable (lua_State *L, const char *tname);
这时会在 registry 创建一个{__name=tname}的表,并在 registry[tname]=new table. 并将这个new table TODO 这里要看看是不是
__tostring
print(t)的时候,直接打印出来
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
- 对非string/table调用时会查看有没有__len这个函数,如果没有就报错
__index,__newindex
__index为 t[xxx] 获取操作,当不存在时调用函数或获取table
__newindex为 t[xxx]=value 设置操作,当不存在key时调用函数
__pairs
- 可以重定义table的pairs ,如果是其它类型,也可以定义。__pairs对应的函数需要返回 function,传入的参数1,nil,
简单例子如下
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能像函数一样调用
local t = {id=2}
setmetatable(t,{
__call = function(a,arg1)
return a.id+arg1
end
})
print(t(10))
--输出
--12
__close
- 在lua5.4中,基于 __gc的调用时机不明确,__close是专门用于一个变量逃出作用域时被调用的函数。正常退出及 break/goto/return 及 抛出错误时,都会被调用。可以看作是golang的defer ,c#的using语句,使用例子如下
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函数抛出异常,则会被外面捕获
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 如果一个coroutine 被 yield 而且从来没有被resume,则不会被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 --因为没有resume,所以不会调用close
end
return 3,4
end)
coroutine.resume(co)
--coroutine.resume(co)
end
testCoroutineClose()
--输出
--close 1
2.4. 如果一个coroutine以error结束,则因为没有清空stack,所以也不会 close 任何变量 这时需要 __gc 或 coroutine.close来实现回收。当然,如果coroutine 是用 coroutine.wrap来创建,那么即使是错误也会有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
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
- 总述 当一个object( table/userdata) 在被gc认为要回收的时候会被调用,因为垃圾回收会发生在进程的任意阶段,所以__gc也可能在进程的任意阶段被调用,在lua_close时,肯定会调用,常用于管理资源比如文件,网络,数据库链接,或lightuserdata.
- __gc这个值需要在 setmatatable 函数调用之前就已经有这个key了,如果先调用setmetatable,后再将mt加上__gc这个key对应的函数,那么是无效的
local function test1()
local t = {}
local mt = {}
setmetatable(t,mt)
mt.__gc = function(t)
print("this line not print")
end
end
test1()
- 当垃圾回收认为一个object需要回收的时候,不会马上对内存进行回收,而是先把这个object放到一个list上去,然后再将从list中逐一检查有没有 __gc这个函数,如果有,就调用。 另list好像是从尾插向头的,第一个调用__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
- 因为在 垃圾回收的cycle中,需要使用这个object, 所以对内存回收是在下一个 垃圾回收 cycle.所以如果你在__gc这个函数中,把这个t又引用到别的地方去(比如global) 那么,它就不会被回收,但是当 __gc这个被调用之后,不会再被调用.
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函数中,除了不能yield之外,啥都可以,例如:报错,创object,甚至显示调用gc. 报错之后,并不会传播出来。只是当前函数在报错之后不会再执行。
local sgmt = {
__gc = function(t)
gv = t
print("global",t.id)
--assert(false,"xxxx") 如果这句不注释,则 下面的那句不会执行
setmetatable(gv,tdmt)
assert(false,"xxxx") --不会有任何报错信息出现
end
}
local function resurrected()
setmetatable({id=5},sgmt)
collectgarbage("collect")
os.execute("sleep 1")
end
resurrected()
__mode
被kv修饰过的,引用了跟没引用一样
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 只要其中一个被回收,如果另一个只能过这个t 来获取,事实上你也取不到另一个。就是说,如果 key是weak ,但value是strong,那么如果key被回收,就算value没有被回收,你也获取不到value 。
- 如果是中间过程中转变weak为strong,也有可能有一些已经被回收(因为这个改动,需要下一个回收cycle才生效)
- 只有会被gc的value才会被weak引用删除。比如table userdata。string也是会被gc的,但是string在这又不会被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
都不知道有啥用,不能直接获取/赋值