• Lua之元表


    Lua之metatable

    一、元表

    Lua中的每个值都有一套预定义的操作集合,也可以通过metatable(元表)来定义一个值的行为,metatable包含了一组meatmethod(元方法)。

    Lua中的每个值都有一个metatable,table和userdata可以有各自独立的metatable,而其他类型的值则共享其类型所属的单一metatable。

    在Lua代码中,只能设置table的metatable,若要设置其他类型的值的metatable,必须通过C代码来完成。

    function

    description

    getmetatable(t)

    获取t的metatable

    setmetatable(t, m)

    设置t的metatable为m

     

     

    Lua标准库中只有string库有默认metatable,其它类型在默认情况下都没有metatable。

    getmetatable('ab')         -- table: 0x1b684b0
    getmetatable(123)          -- nil
    getmetatable({1})          -- nil

     

    下面是一个例子,用table模拟集合操作,并利用metatable实现了交集、并集运算:

    Set = {}
    
    local mt = {}       -- metatable
    
    function Set.new(l)
        local set = {}
        setmetatable(set, mt)       -- set metatable for all "set" type
        for _,v in ipairs(l) do set[v] = true end
        return set
    end
    
    function Set.union(a,b)
        local res = 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{}
        for k in pairs(a) do res[k] = b[k] end 
        return res 
    end
    
    function Set.sub(a, b)
        for k in pairs(a) do  
            if not b[k] then return false end 
        end 
        return true
    end
    
    function Set.tostring(set)
        local l = {}
        for e in pairs(set) do
            l[#l + 1] = e 
        end 
        return "{" .. table.concat(l, ",") .. "}" 
    end
    
    mt.__add = Set.union
    mt.__mul = Set.intersection
    mt.__le =  Set.sub
    mt.__tostring = Set.tostring
    
    s1 = Set.new{10,20,30,50}
    s2 = Set.new{30, 1}
    s3 = Set.new{20,50}
    
    print(getmetatable(s1))     -- 0x12e6cb0
    print(getmetatable(s2))     -- 0x12e6cb0
    
    print(s1+s2)     -- {1,30,10,50,20}
    print(s1*s2)     -- {30}
    
    print(s3<=s1)    -- true

    在metatable中,每种算术操作都有对应的字段名,除了上述的__add和__mul外,还有其它的一些字段,如下表:

    metamethods

    description

    __add

    +

    __sub

    -

    __mul

    *

    __div

    /

    __unm

    __pow

    __mod

    __concat

    定义连接行为

    __eq

    ==

    __lt

    __le

    <=

    __tostring

    供print函数调用

    __metatable

    如果设置了该字段,将禁止查看、修改该元表

    metatable中关系运算符只支持 ==、< 、<=,Lua会将 a~=b转化为 not (a==b),将a>b转化为b<a,将a>=b转化为b<=a。

    当一个算术表达式中混合了具有不同metatable的值时,例如

    s = Set.new{1,2,3}
    s = 8 + s

    Lua会按照如下步骤查找metatable

    1、如果第一个值有metatable,并且有__add字段,那么就使用该字段作为meatmethod;
    2、如果第一个值没有metatable,但第二个值有metatable,并且有__add字段,那么就使用该字段作为meatmethod;
    3、如果两个值都没有metatable,Lua就引发一个错误。

    因此,上例会调用 Set.union,但在Set.union 函数内部会发生错误!

    与算术类的meatmethod不同,关系类的meatmethod不能应用于混合的类型。如果试图将一个字符串与一个数字作顺序比较,Lua会引发一个错误。同样,如果试图比较两个具有不同meatmethod的对象,Lua也会引发一个错误。

    注意,等于比较永远不会引发错误。如果两个对象具有不同的meatmethod,直接返回false。

     


     

    二、访问table的元方法

     

    当访问table中一个不存在的字段时,会调用其metatable的__index元方法,如果没有这个元方法,返回nil。否则,就由__index元方法提供查找结果。

    Window = {}
    Window.prototype = {x=0, y=0, width=100, height=100}
    Window.mt = {}
    
    Window.mt.__index = function(table, key)
        return Window.prototype[key]
        end 
    
    function Window.new(o)
        setmetatable(o, Window.mt)
        return o
    end
    
    
    w1 = Window.new({x=10, y=20, width=37})
    print(w1.width)     -- 37
    
    w2 = Window.new({x=10, y=20})
    print(w2.width)     -- 100

     print(rawget(w1, 'width')) -- 37
     print(rawget(w2, 'width')) -- nil

    注意:如果不想使用元表,可以使用rawget方法,它对table进行一次不考虑元表的简单访问。

    在Lua中,将__index元方法用于继承是很普遍的方法,__index元方法不必一定是函数,也可以是一个table:
    当__index是一个函数时,以table和不存在的key作为参数来调用该函数;
    当__index是一个table时,Lua就以相同的方式来重新访问这个table,因此,上面的例子的__index元方法也可以如下: 

    Window.mt.__index = Window.prototype

    当对table中一个不存在的索引赋值时,会查找__newindex元方法,如果有这个元方法,就调用它,而不是执行赋值。

    如果__newindex是一个table,就在此table中执行赋值,而不是对原来的table。

    类似rawget,调用rawset(t,k,v),可以不涉及任何元方法而直接设置t[k]=v。

    组合使用__index和__newindex元方法可以实现一些强大的功能,比如:只读table,具有默认值的table、面向对象编程中的继承等。

    例子一、具有默认值的table:

    local mt = {__index=function() return t.___ end}
    
    function setDefault(t,d)
        t.___ = d
        setmetatable(t, mt)
    end 
    
    tab = {x=10, y=20}
    print(tab.x, tab.z) --10, nil
    
    setDefault(tab, 0)
    print(tab.x, tab.z) -- 10, 0

    注意: __index和__newindex都是在table中没有所需访问的索引时才发挥作用的。

    例子二,通过一个空table来代理对原table的访问,从而来跟踪对table的访问过程:

    t = {1,3,4}
    
    local _t = t
    
    t = {} -- proxy
    
    local mt = {
        __index = function(t,k) print("access to element " .. tostring(k)) return _t[k] end,
        __newindex = function(t,k,v) print("update of element " .. tostring(k) .. " to " .. tostring(v)) _t[k] = v end,
    }       
    
    setmetatable(t, mt)
        
    t[1] = "hello"
    print(t[1])

    输出为:

    update of element 1 to hello
    access to element 1
    hello

    例子三,实现一个只读table:

    function readOnly(t)
    
        local proxy = {}
        local mt = {
            __index = t,
            __newindex = function(t,k,v) error("attempt to update a read-only table", 2) end
        }       
        setmetatable(proxy, mt)
        return proxy
    end 
        
    
    days = readOnly{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}
    print(days[1])
    days[2] = "No"    -- error
  • 相关阅读:
    高质量动漫实时画质增强器Anime4K在mpv上的配置
    grep中正则表达式使用尖括号表示一个单词
    虚拟机复制的linux无法联网,解决Bringing up interface eth0: Device eth0 does not seem to be present, delaying initialization.
    Linux将动态IP改为静态IP
    回车、换行的区别
    栈的链接存储
    栈的顺序存储
    冒泡排序
    插入排序
    双向循环链表
  • 原文地址:https://www.cnblogs.com/chenny7/p/4045913.html
Copyright © 2020-2023  润新知