lua元表

lua的任何值都可以设置元表,其中table用 setmetatable, 其它类型使用debug.setmetatable.
通过这个,可以获得跟cpp operator重载一样的骚操作。比如,可以给一个number 设置一个元表,实现__add方法,但是__add中用减法,这样,当一个number被设置元表后,进行加法运算,可以得到减法的结果。


元表的key分类

  1. 运算
    __add,__div,__idiv,__mul,__sub,__pow,__unm,__mod
  2. 二进制运算
    __band,__bnot,__bor,__bxor,__shl,__shr
  3. 比较
    __eq,__lt,__le
  4. 字符串相关
    __name,__tostring,__concat
  5. 表操作
    __len,__index,__newindex,__pairs
  6. 其它
    __call,__close,__gc,__mode,__metatable

详细解释用法

通用类

运算/二进制运算/比较/__concat
这几个都是比较简单的,函数类似:function(lvalue,rvalue) return xxx end 这样的

字符串相关
__name
  1. 下面是一个简单的例子
local t = {id=1}
local mt = {
    __name="newTypeName",
}
setmetatable(t,mt)
print(type(t),t)  --输出   table   newTypeName: 0x55a032ead330
  1. 但是更经常的是在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
  1. 对非string/table调用时会查看有没有__len这个函数,如果没有就报错
__index,__newindex

__index为 t[xxx] 获取操作,当不存在时调用函数或获取table
__newindex为 t[xxx]=value 设置操作,当不存在key时调用函数

__pairs
  1. 可以重定义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()

输出如下


a.png
其它
__call

使非function能像函数一样调用

local t = {id=2}
setmetatable(t,{
        __call = function(a,arg1)
            return a.id+arg1
        end
    })

print(t(10))
--输出
--12
__close
  1. 在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
  1. 其它特性:
    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

输出如下


close1.png

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
  1. 总述 当一个object( table/userdata) 在被gc认为要回收的时候会被调用,因为垃圾回收会发生在进程的任意阶段,所以__gc也可能在进程的任意阶段被调用,在lua_close时,肯定会调用,常用于管理资源比如文件,网络,数据库链接,或lightuserdata.
  2. __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()
  1. 当垃圾回收认为一个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
  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
  1. 在__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()
  1. 如果k/v 任一weak 只要其中一个被回收,如果另一个只能过这个t 来获取,事实上你也取不到另一个。就是说,如果 key是weak ,但value是strong,那么如果key被回收,就算value没有被回收,你也获取不到value 。
  2. 如果是中间过程中转变weak为strong,也有可能有一些已经被回收(因为这个改动,需要下一个回收cycle才生效)
  3. 只有会被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

都不知道有啥用,不能直接获取/赋值

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容

  • 前言 元表对应的英文是metatable,元方法是metamethod。我们都知道,在C++中,两个类是无法直接相...
    胤醚貔貅阅读 1,004评论 0 2
  • 简介 模块库类似一个封装库,存放公用代码,以api接口形式被其他调用 元表 元表(metatable)提供两个ta...
    叫我颜先生阅读 258评论 0 0
  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 13,783评论 0 38
  • lua中每个值都可以拥有一个元表,元表是一个普通的lua table,定义了原始值在特定操作下的行为。 setme...
    我和我的火柴阅读 382评论 0 0
  • 前言 元表对应的英文是metatable,元方法是metamethod。我们都知道,在C++中,两个类是无法直接相...
    BobWong阅读 1,036评论 0 9