• Lua笔记——3.元表Metatable


    metatable简介

    1.在Lua 中每个 value( table 和 userdata ) 都可以有一个属于自己的 metatable,相同类型的value共享一个属于本类型的 metatable。初始情况下,string 类型有自己的 metatable,而其他类型则为nil。

    metatableValue.PNG

    2.任何一个表都可以是其他一个表的metatable,一组相关的表可以共享一个metatable(描述他们共同的行为)。一个表也可以是自身的metatable(描述其私有行为)。

    3.在 Lua 代码中,通过调用 setmetatable 来设置且只能设置 table 的 metatable,C/C++ 调用 Lua C API 则可以设置所有 value 的 metatable。

    lua的metatable功能,主要有以下几种作用

    1.使用metatable控制对table的访问

    当查询table的某个键的时候,如果该table的这个键没有值,那么Lua就会寻找该table的metatable中的__index元方法:如果__index指向一个table,Lua会在该table中查找相应的键, 并且,__index也可以指向一个方法

    查询

    __index元方法

    __index指向表时

    查询顺序:当我们在一个表中查找一个元素时
    首先在该表中查找,有则返回对应值,无则查看该表是否有元表metatable
    若无元表则返回nil
    有元表时,lua并不是在其元表中查找,而是在其元表的__index域中查找
    如果__index指向一个表,Lua会在此表中查找相应的键。(__index也可以指向一个方法)

    如果只为一个表设置元表,而不定义__index域,则无法查询到想要的结果

    BaseClass = {theKey1 = "the string value1"}
    --[[
    --Lua默认创建一个不带metatable的新表
    DerivedClass = {}
    --使用setmetatable方法为一个表设置metatable
    setmetatable(DerivedClass,BaseClass)
    ]]--
    --注释的两句可简写为:
    DerivedClass = setmetatable({},BaseClass)
    
    res = DerivedClass.theKey1
    print(res)  -->nil
    

    输出示例:
    derivedclass.PNG

    为该表的元表设置对应的__index键之后:

    BaseClass = {theKey1 = "the string value1"}
    BaseClass.__index = BaseClass
    DerivedClass = setmetatable({},BaseClass)
    res = DerivedClass.theKey1
    print(res)
    

    也可以换一种简写方法——匿名元表:

    BaseClass = {theKey1 = "the string value1"}
    --即直接设  AnonymousTable = {__index = BaseClass} 为DerivedClass的元表,查找时可直接在AnonymousTable中的__index域对应的表BaseClass中查找
    DerivedClass = setmetatable({},{__index = BaseClass})
    res = DerivedClass.theKey1
    print(res)
    

    setmetatable.png

    所以,lua的继承可以表示为:

    --file:deriveTest.lua
    local Car = {}
    Car.__index = Car
    
    function Car.New(o)
        o = o or {}
        setmetatable(o,Car)
        return o
    end
    
    function Car.Run()
        print("Car's run func.")
    end
    
    --使用继承
    --直接调用“父类”中的run方法
    Car.Run()
    
    local FordCar = Car.New()
    --子类调用“父类”中的run方法
    FordCar.Run()
    
    --为子类fordCar写入新的
    run方法
    function FordCar.Run()
        print("FordCar's run func.")
    end
    
    --重写之后调用
    FordCar.Run()
    

    deriveclass.PNG

    __index指向一个方法时

    __index指向一个方法时,在我们访问该 table 的不存在的域时,Lua 会尝试调用__index 元方法metamethod (__index 元方法可以接收两个参数 table 和 key)

    --indexFunc.lua
    local t1 = {}
    t1.__index = function(table,key)
        print("call the __index metamethod")
        print("table"..tostring(table))
        print("key"..key)
        return key.." from the __index"
    end
    --set t1 as t2's metatable
    local t2 = setmetatable({},t1)
    print("table t2 "..tostring(t2))
    --pass the table and the key to __indexmetamethod
    local res = t2.key1
    print("the result is :"..res)
    print("------------------")
    res = t2.key2
    print("the result is :"..res)
    

    输出结果:

    indexFunc

    赋值

    当对table的某个键进行赋值时,如果该table存在该字段则为之赋值,若没有,那么Lua就会寻找该table元表metatable中的__newindex元方法:如果__newindex指向一个table时,Lua会对这个table进行赋值操作,如果__newindex指向一个方法,Lua则会调用该方法

    __newindex

    如果对 table 的一个不存在的域赋值时,Lua 将检查 __newindex metamethod:

    如果 __newindex 指向 table

    Lua 将对此 table 进行赋值

    --file:newindex.lua
    local mt = {
        key1 = "key1's value"
    }
    mt.__index = mt
    
    local other = {
        key1 = "the old value"
    }
    
    mt.__newindex = other
    --[[
    mt.__newindex = function()
        print "Can not set value"
    end
    --]]
    
    local t = setmetatable({},mt)
    
    
    t.key1 = "the new value from the table t."
    --该操作会对 表t 的 元表mt 下__newindex域所指table中的key1进行更新操作
    --但是 表t 中仍然没有键key1,进行t.key1的查询时,查询的仍然是__index中对应的键key1的值
    print(mt.key1)  -->key1's value
    print(t.key1)   -->key1's value
    print(rawget(t,"key1")) -->nil
    print(other.key1)   -->the new value from the table t.
    

    输出结果:
    newindex

    如果 __newindex指向 函数

    __newindex指向函数时,Lua 将调用函数该函数可以接受三个参数 table key value。如果希望忽略__newindex 方法对 table 的域进行赋值,可以调用 rawset(t, k, v)

    __index 和__newindex结合实现OOP等

    1.OOP
    2.Read-only table
    3.Tables with default values

    OOP

    使用 ' : '冒号来声明或者调用的函数,会自动传入“调用者”table自身,即self

    --file:LuaClass.lua
    --声明Lua类,这里声明了类名还有属性,并且给出了属性的初始值。
    LuaClass = {x = 0, y = 0}
    
    --定义LuaClass的元表的__index索引为自身,仅在自身上查相应属性
    LuaClass.__index = LuaClass
    
    --构造函数New()
    --如果在声明时使用了冒号,而在调用时使用点来调用,则会报错:找不到名为self的定义以及变量
    function LuaClass:New(x, y)
        local temp = {};    --构造临时表,如果没有这句,那么类所建立的对象改变,其他对象都会改变
        setmetatable(temp, LuaClass);  --将临时表temp的元表metatable设定为LuaClass
        self.x = x;
        self.y = y;
        return temp;    --返回自身
    end
    
    --测试打印方法--
    function LuaClass:Test()
        print("x: " .. self.x .. " y: " .. self.y);
    end
    

    测试代码:

    --file: testClass.lua
    require "LuaClass"
    
    local luaClass = LuaClass:New(31,60);
    
    luaClass:Test();
    

    输出结果:
    testClass

    rawget和rawset的使用

    --file: raw.lua
    datas = {}
    datas.mt = {}
    
    function datas.new(o)
        o = o or {}
        setmetatable(o,datas.mt)
        return o
    end
    
    datas.mt.__index = function(t,k)
            return 'default'
        end
    
    datas.mt.__newindex = function(t,k,v)
            if k == "test" then
                rawset(t,"test","value")
            end
        end
    
    d = datas.new()
    
    --查询table d 中不存在的元素 x 时,会调用__index指向的函数
    print(d.x)  -->defaule
    
    --调用了rawget(t,k,v),绕过了元表的__index和__newindex,因为table d为空表,所以返回nil
    print(rawget(d,d.test)) -->nil
    
    --为table d中不存在的元素 test 赋值,调用元表对应的__newindex,参数" t = d , k = test , v = nil"
    --元表中的__newindex对应的函数调用rawset,绕过元表,直接为table d  赋值,此时d["test"] = "vale"
    d.test = "something"
    
    --table d中“test”对应的值为“value”
    print(d.test)   --value
    
    --table d中“test”对应的值为“value”
    print(rawget(d,"test")) --value
    

    rawFunc.png

    2. 为 Lua 函数库提供支持

    Lua 库可以定义和使用 metamethod 来完成一些特定的操作
    一个典型的例子是 Lua Base 库中 __tostring 函数,print 函数会调用此函数进行输出,调用print时,会检查并调用__tostring metamethod

    --file:tablelib.lua
    local mt = {}
    mt.__tostring = function(t)
        return '{'..table.concat(t,',')..'}'
    end
    
    local t = {1,2,3}
    print(t)
    print("-----------------")
    
    setmetatable(t,mt)
    print(t)
    

    tablelib.PNG

    3. 重载算数运算符和关系运算符的行为

    算术运算符的metamethods

    例如:Lua 尝试对两个 table 进行加操作时,它会按顺序检查这两个 table 中是否有一个存在 metatable 并且这个 metatable 是否存在 __add 域,如果 Lua 检查到了这个__add 域,那么会调用它,这个域被叫做 metamethod。(Metamethod 的参数为操作数)
    eg:加号重载

    --file: operAndMeth.lua
    
    local mt = {}
    function addOne(a,b)
        return 'table+'..b
    end
    
    mt.__add = addOne
    
    --[[
    function mt.__add(a,b)
        return 'table+'..b
    end
    --]]
    local t ={}
    setmetatable(t,mt)
    print(t+1)  -->table+1
    

    其他操作符:
    opermethod.png

    --file:operators.lua
    Set = {}
    
    Set.mt = {}
    
    function Set.new(t)
        local set = {}
        setmetatable(set,Set.mt)    -->Make the Set.mt handle the lookup feature
        for _,v in ipairs(t) do
            set[v] = true
        end
        return set
    end
    
    function Set.union(a,b)
        local res = Set.new{}    --It will be error if you use " = Set.new()"
        for k in pairs(a) do
            res[k] = true
        end
    
        for k in pairs(b) do
            res[k] = true
        end
    
        return res
    end
    
    function Set.intersection(a,b)
        local res = Set.new{}    --It will be error if you use " = Set.new()"
        for k in pairs(a) do
            res[k] = b[k]
        end
        return res
    end
    
    function Set.tostring (set)
        local s = "{"
        local sep = ""
        for e in pairs(set) do
            s = s..sep..e
            sep = ","
        end
        return s.."}"
    end
    
    function Set.print(s)
        print(Set.tostring(s))
    end
    
    Set.mt.__add = Set.union
    Set.mt.__mul = Set.intersection
    
    s1 = Set.new{1,2,3,4,5,6}
    s2 = Set.new{5,6,7,8,9}
    
    print(getmetatable(s1))    -->table:02749908
    print(getmetatable(s2))    -->table:02749908
    
    --[[
    Lua选择metamethod的原则:如果第一个参数存在带有__add域的metatable,Lua使用它作为metamethod,和第二个参数无关;
    --为避免该错误可以在Set.union中进行判断
    
    function Set.union(a,b)
        if getmetatable(a) ~= Set.mt or getmetatable(b) ~= Set.mt then
        error("Attempt to add a Set with a non-set value",2)
        end
        --之前的代码
    end
    
    --]]
    s3 = s1+s2    --It will be error if you write s3 = s1 + 1
    s4 = s1*s2
    Set.print(s3)    -->{1,2,3,4,5,6,7,8,9}
    Set.print(s4)    -->{5,6}
    

    关系运算符的metamethods

    Metatables允许我们使用metamethods:__eq(等于),__lt(小于),和__le(小于等于)给关系运算符赋予特殊的含义。对剩下的三个关系运算符没有专门的metamethod,Lua将a ~= b转换为not (a == b);a > b转换为b < a;a >= b转换为 b <= a。

    --在file:operators.lua后添加以下lua代码,即可判断集合的包含问题 :<=代表集合的包含:a <= b表示集合a是集合b的子集。这种意义下,可能a <= b和b < a都是false;因此,我们需要将__le和__lt的实现分开:
    print("____________________")
    
    Set.mt.__le = function(a,b) --b include or equal a
        for k in pairs(a) do
            if not b[k] then
                return false
            end
        end
        return true
    end
    
    Set.mt.__lt = function(a,b)
        return a <= b and not (b <= a)
    end
    
    Set.mt.__eq = function(a,b)
        return a <=b and b <= a
    end
    
    print(s3 >= s4) -->true
    print(s3 < s4)  -->false
    print(s3 > s4)  -->true
    print(s3 == s4) -->false
    

    REF

    http://lua-users.org/wiki/

    http://www.jb51.net/article/56690.htm

  • 相关阅读:
    python-常用数据类型
    python入门篇
    Vue 架构
    Bootstrap Web框架
    策略模式
    Java线程安全总结
    JVM中线程状态转换图
    java 多线程并发系列之 生产者消费者模式的两种实现
    JVM 垃圾回收器详解
    MyISAM和InnoDB索引实现对比
  • 原文地址:https://www.cnblogs.com/sylvan/p/8478366.html
Copyright © 2020-2023  润新知