• Lua中的元表,弱表,面向对象


    元表在我们平时的开发中应用的不多,最熟悉的要数lua中的面向对象实现。今天就总结下metatable的使用,底层原理,以及使用场景。

    metatable是什么?

     简单一句话,是lua提供给我们的一种操作table的方法。

    metatable也是table,从源码中我们看到:

    typedef struct Table {
      CommonHeader;
      lu_byte flags;  /* 1<<p means tagmethod(p) is not present */
      lu_byte lsizenode;  /* log2 of size of 'node' array */
      unsigned int sizearray;  /* size of 'array' array */
      TValue *array;  /* array part */
      Node *node;
      Node *lastfree;  /* any free position is before this position */
      struct Table *metatable;
      GCObject *gclist;
    } Table;

    metatable相关的api只有两个:getMetatablesetMetatable

    getmetatable (object)
    
    If object does not have a metatable, returns nil. 
    Otherwise, if the object's metatable has a "__metatable" field, returns the associated value. 
    Otherwise, returns the metatable of the given object.
    setmetatable (table, metatable)
    Sets the metatable
    for the given table. (You cannot change the metatable of other types from Lua, only from C.) If metatable is nil, removes the metatable of the given table. If the original metatable has a "__metatable" field, raises an error.

    两个方法都提到了__metatable。使用__metatable可以保护元表,禁止用户访问元表中的成员或者修改元表。 

    metamethod

    元表中预定义了一些元方法,我们也只能对这些元方法自定义。我参照源码中的枚举值给出对应的lua中的元方法名,以及对应的使用场景:

    enum metamethod application
    TM_INDEX
    __index   t[key],取值
    TM_NEWINDEX
    __newindex t[key] = value, 赋值
    TM_GC
    __gc collectgarbage
    TM_MODE
    __mode   weak table
    TM_LEN
    __len   #
    TM_EQ
    __eq ==
    TM_ADD
    __add +
    TM_SUB
    __sub -
    TM_MUL
    __mul *
    TM_MOD
    __mod %
    TM_POW
    __pow ^
    TM_DIV
    __div /
    TM_IDIV
    __idiv   //   向下取整除法 
    TM_BAND
    __band &   按位与
    TM_BOR
    __bor |    按位或
    TM_BXOR
    __bxor ~   按位异或
    TM_SHL
    __shl <<  左移
    TM_SHR
    __shr >>  右移
    TM_UNM
    __unm -      取负
    TM_BNOT
    __bnot ~     按位非
    TM_LT
    __lt <     小于
    TM_LE
    __le <=   小于等于
    TM_CONCAT
    __connect ..
    TM_CALL
    __call func(args),  非函数类型的函数调用

     

    下面我要对其中几个有代表性的元方法自定义实现。

     __add:

    local mt = {
        __add = function(table1, table2)
           for i=1, #table2 do
                table1[#table1 + 1] = table2[i]
           end
           return table1
        end
    }
    setmetatable(t1, mt)
    setmetatable(t2, mt)
    
    local t3 = t1 + t2
    printTable("_add t3", t3)

     __add的元方法很像c++中成员函数形式的运算符重载。

    值得注意的一点是,后者对于运算符两边的操作数的顺序是有要求的,运算符其实是施加在左操作数的。

    lua中没有这种限制。在该例子中,如果只对t1和t2中的某一个设置元表,可以达到同样的效果。这里的原因就要追溯到lua的源码。

    因为lua的代码经过解释器解释后,会生成一系列的指令集合。指令是由操作码和参数组成,类似于:

      Op | param1 | param2 | ...

    加法的操作码是OP_ADD,lua虚拟机处理该操作的步骤是:

    1、如果两个操作数都是整数,直接相加

    2、否则,如果两个操作数都可以转换为数字(tonumber),转换后相加。

    3、否则,依次检查两个操作数是否有元表,元表中是否有__add。如果有,则将两个操作数传入元表。

      所以只要其中一个操作数有元表和__add方法就可以。

    具体可以参考lua的源码lvm.c和ltm.c。

    __gc :

    local t3 = { _name = "t3"}
    local mt = {
        __gc = function(t)
            print(t._name)
        end
    }
    setmetatable(t3, mt)
    t3 = nil
    collectgarbage("collect")

    __tostring:

    print(t1)
    local mt = {
        __tostring = function(t)
            local ret = ""
            for k,v in pairs(t) do
                print("__tostring", v)
                ret = ret .. v  -- 自定义打印
            end
            return ret
        end
    }
    setmetatable(t1, mt)
    print(t1) 

    __newindex: 

    local newindexTable = {}
    local mt = {
        __newindex = function(t, k, v)
            if k ~= 3 then
                -- t[k] = v  -- 死循环
                rawset(t, k, v)
            else
                print("can not assign", k)
            end
        end,
        -- __newindex = newindexTable -- 不对t1赋值。对newindexTable赋值。
    } setmetatable(t1, mt) t1[3] = 3.3

    __index: 可以是table,也可以是function

    local mt = {
        -- __index = {1, 2, 3.3, 4.4}
    
        __index = function(t, key)
            return key
        end
    }
    setmetatable(t1, mt)
    print(t1[3])

    lua面向对象

    -- inherit from table
    -- local SceneController = class("SceneController")
    -- local JJGameSceneController = class("JJGameSceneController", jj.sdk.SceneController)
    
    
    -- inherit from function
    -- local SceneBase = class("SceneBase", function()
    --     return display.newScene("SceneBase")
    -- end)
    -- SceneBase的类型是table,因为class返回cls
    -- SceneBase.new 返回是userdata
    -- 只要是继承的顶端是c++对象(userdata),new出来的对象都是userdata
    -- local JJGameSceneBase = class("JJGameSceneBase", require("sdk.scenestack.SceneBase"))
    
    function class(classname, super)
        local superType = type(super)
        local cls
    
        if superType ~= "function" and superType ~= "table" then
            superType = nil
            super = nil
        end
    
        local parents = {}
        -- superType == "function", 对应上面的SceneBase
        -- (super and super.__ctype == 1), 对应JJGameSceneBase
    
        if superType == "function" or (super and super.__ctype == 1) then
            -- inherited from native C++ Object
            cls = {}
    
            if superType == "table" then 
                -- copy fields from super
                for k,v in pairs(super) do cls[k] = v end
                cls.__create = super.__create
                cls.super    = super
            else
                cls.__create = super -- super is function
                cls.ctor = function() end
            end
    
            cls.__cname = classname
            cls.__ctype = 1
    
            function cls.new(...)
                local instance = cls.__create(...)
                -- copy fields from class to native object
                for k,v in pairs(cls) do instance[k] = v end
                instance.class = cls
                instance:ctor(...)
                return instance
            end
    
        else
            -- 对应SceneController 和 JJGameSceneController
            -- inherited from Lua Object
            if super then
                cls = {}
                setmetatable(cls, {__index = super})
                cls.super = super
            else -- 无继承
                cls = {ctor = function() end} -- 默认构造函数
            end
    
            cls.__cname = classname
            cls.__ctype = 2 -- lua
            cls.__index = cls
    
            function cls.new(...)
                local instance = setmetatable({}, cls) -- 返回第一个参数。
                instance.class = cls    -- 类table, 取类名 cls.class.__cname
                instance:ctor(...)      -- 自定义ctor
                return instance
            end
        end
    
        return cls
    end

    多重继承

    lua 弱表:通过__mode设置表的key和value的弱引用性质

    t ={}
    setmetatable(t, { __mode ='v'}) 
    
    do
        local someval = {1,2}
        t['foo'] = someval 
    end
    
    collectgarbage()
    
    for k, v in pairs(t) do
        print(k, v)
    end
    • someval是do end代码块中的局部变量,代码块结束,生命周期也就结束了,对{1,2}的引用也就没了。
    • 表t的value设置为了弱引用,不会影响{1,2}的回收
    • 当申请的内存{1,2} 没有引用(someval和t['foo'])指向它时,会在gc周期中被回收。
    local names = setmetatable({}, {__mode = 'k'})
    -- local names = {}
    
    function name(obj, str)
        names[obj] = tostring(str)
        return obj
    end
    
    local _print = print
    function print(...)
        local arg = {...}
        for i=1, #arg do
            local name = names[arg[i]]
            if name then arg[i] = name end
        end
        _print(table.unpack(arg))
    end
    
    t1 = name({}, "T1")
    t2 = {}
    t3 = {}
    name(t2, "T2")
    name(t3, "T3")
    
    print(t1, t2, t3)

     

     

    Reference:

    1、http://www.lua.org/manual/5.1/manual.html#2.8

    2、http://lua-users.org/wiki/MetatableEvents

    3、http://lua-users.org/wiki/GeneralizedPairsAndIpairs

    4、http://book.luaer.cn/_161.htm

    5、http://lua-users.org/wiki/WeakTablesTutorial

     

  • 相关阅读:
    UITableView学习笔记
    IOS基础之设置APP的名字、设置图标、添加等待加载时的图片
    UIScrollView,UIPageControl
    UIPickerView基本用法
    最大公约数和最小公倍数
    快速幂、快速乘
    素数筛
    最小生成树
    BZOJ1070 [SCOI2007]修车
    BZOJ1109 [POI2007]堆积木Klo
  • 原文地址:https://www.cnblogs.com/jimobuwu/p/8980808.html
Copyright © 2020-2023  润新知