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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

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