• Lua中的metatable详解


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

    Lua 中 metatable 是一个普通的 table,但其主要有以下几个功能:

    1.定义算术操作符和关系操作符的行为
    2.为 Lua 函数库提供支持
    3.控制对 table 的访问

    Metatables 定义操作符行为

    Metatable 能够被用于定义算术操作符和关系操作符的行为。例如:Lua 尝试对两个 table 进行加操作时,它会按顺序检查这两个 table 中是否有一个存在 metatable 并且这个 metatable 是否存在 __add 域,如果 Lua 检查到了这个 __add 域,那么会调用它,这个域被叫做 metamethod。

    Lua 中每个 value 都可以有一个 metatable(在 Lua 5.0 只有 table 和 userdata 能够存在 metatable)。每个 table 和 userdata value 都有一个属于自己的 metatable,而其他每种类型的所有 value 共享一个属于本类型的 metatable。在 Lua 代码中,通过调用 setmetatable 来设置且只能设置 table 的 metatable,在 C/C++ 中调用 Lua C API 则可以设置所有 value 的 metatable。默认的情况下,string 类型有自己的 metatable,而其他类型则没有:

    print(getmetatable('hi')) --> table: 003C86B8
    print(getmetatable(10))  --> nil

    Metamethod 的参数为操作数(operands),例如:

    local mt = {}
    function mt.__add(a, b)
        return 'table + ' .. b
    end
    local t = {}
    setmetatable(t, mt)
    print(t + 1)

    每个算术操作符有对应的 metamethod:

    + __add
    * __mul
    - __sub
    / __div
    - __unm (for negation)
    % __mod
    ^ __pow

    对于连接操作符有对应的 metamethod:__concat

    同样,对于关系操作符也都有对应的 metamethod:

    == __eq
    < __lt
    <= __le

     

    其他的关系操作符都是用上面三种表示:
    a ~= b 表示为 not (a == b)
    a > b 表示为 b < a
    a >= b 表示为 b <= a

    和算术运算符不同的是,关系运算符用于比较拥有不同的 metamethod(而非 metatable)的两个 value 时会产生错误,例外是比较运算符,拥有不同的 metamethod 的两个 value 比较的结果是 false。

    不过要注意的是,在整数类型的比较中 a <= b 可以被转换为 not (b < a),但是如果某类型的所有元素并未适当排序,此条件则不一定成立。例如:浮点数中 NaN(Not a Number)表示一个未定义的值,NaN <= x 总是为 false 并且 x < NaN 也总为 false。

    为 Lua 函数库提供支持

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

    local mt = {}
    mt.__tostring = function(t)
        return '{' .. table.concat(t, ', ') .. '}'
    end
     
    local t = {1, 2, 3}
    print(t)
    setmetatable(t, mt)
    print(t)

    另外一个例子是 setmetatable 和 getmetatable 函数,它们定义和使用了 __metatable 域。如果你希望设定的 value 的 metatable 不被修改,那么可以在 value 的 metatable 中设置 __metatable 域,getmetatable 将返回此域,而 setmetatable 则会产生一个错误:

    mt.__metatable = "not your business"
    local t = {}
    setmetatable(t, mt)
    print(getmetatable(t)) --> not your business
    setmetatable(t, {})
        stdin:1: cannot change protected metatable

    看一个完整的例子:

    Set = {}
     
    local mt = {}
     
    function Set.new(l)
        local set = {}
        -- 为 Set 设置 metatable
        setmetatable(set, mt)
        for _, v in ipairs(l) do set[v] = true end
        return set
    end
     
    function Set.union(a, b)
        -- 检查 a b 是否都是 Set
        if getmetatable(a) ~= mt or getmetatable(b) ~= mt then
            -- error 的第二个参数为 level
            -- level 指定了如何获取错误的位置
            -- level 值为 1 表示错误的位置为 error 函数被调用的位置
            -- level 值为 2 表示错误的位置为调用 error 的函数被调用的地方
            error("attempt to 'add' a set with a not-set value", 2)
        end
        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
     
    mt.__add = Set.union
    mt.__mul = Set.intersection
     
    mt.__tostring = function(s)
        local l = {}
        for e in pairs(s) do
            l[#l + 1] = e
        end
        return '{' .. table.concat(l, ', ') .. '}'
    end
     
    mt.__le = function(a, b)
        for k in pairs(a) do
            if not b[k] then return false end
        end
        return true
    end
     
    mt.__lt = function(a, b)
        return a <= b and not (b <= a)
    end
     
    mt.__eq = function(a, b)
        return a <= b and b <= a
    end
     
    local s1 = Set.new({1, 2, 3})
    local s2 = Set.new({4, 5, 6})
    print(s1 + s2)
    print(s1 ~= s2)

    控制 table 的访问

    __index metamethod

    在我们访问 table 的不存在的域时,Lua 会尝试调用 __index metamethod。__index metamethod 接受两个参数 table 和 key:

    local mt = {}
    mt.__index = function(table, key)
        print('table -- ' .. tostring(table))
        print('key -- ' .. key)
    end
     
    local t = {}
    setmetatable(t, mt)
    local v = t.a

    __index 域也可以是一个 table,那么 Lua 会尝试在 __index table 中访问对应的域:

    local mt = {}
    mt.__index = {
        a = 'Hello World'
    }
     
    local t = {}
    setmetatable(t, mt)
    print(t.a) --> Hello World

    我们通过 __index 可以容易的实现单继承(类似于 JavaScrpit 通过 prototype 实现单继承),如果 __index 是一个函数,则可以实现更加复杂的功能:多重继承、caching 等。我们可以通过 rawget(t, i) 来访问 table t 的域 i,而不会访问 __index metamethod,注意的是,不要太指望通过 rawget 来提高对 table 的访问速度(Lua 中函数的调用开销远远大于对表的访问的开销)。

    __newindex metamethod

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

    1.如果 __newindex 为函数,Lua 将调用函数而不是进行赋值
    2.如果 __newindex 为一个 table,Lua 将对此 table 进行赋值

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

    结合 __index 和 __newindex 可以实现很多功能,例如:

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

    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', 'Tues', 'Wed', 'Thur', 'Fri', 'Sat'}
    print(days[1])
    days[2] = 'Noday' --> stdin:1: attempt to update a read-only table

    有时候,我们需要为 table 设定一个唯一的 key,那么可以使用这样的技巧:

    local key = {} -- unique key
    local t = {}
    t[key] = value
  • 相关阅读:
    Android后台保活实践总结:即时通讯应用无法根治的“顽疾”
    新手入门:史上最全Web端即时通讯技术原理详解
    【原创】NIO框架入门(四):Android与MINA2、Netty4的跨平台UDP双向通信实战
    【原创】NIO框架入门(三):iOS与MINA2、Netty4的跨平台UDP双向通信实战
    【原创】NIO框架入门(二):服务端基于MINA2的UDP双向通信Demo演示
    【原创】NIO框架入门(一):服务端基于Netty4的UDP双向通信Demo演示
    爱奇艺技术分享:轻松诙谐,讲解视频编解码技术的过去、现在和将来
    网络编程懒人入门(十二):快速读懂Http/3协议,一篇就够!
    美团点评的移动端网络优化实践:大幅提升连接成功率、速度等
    IM开发宝典:史上最全,微信各种功能参数和逻辑规则资料汇总
  • 原文地址:https://www.cnblogs.com/sevenyuan/p/7274846.html
Copyright © 2020-2023  润新知