当前位置: 首页 > 编程笔记 >

Lua中的metatable详解

漆雕嘉茂
2023-03-14
本文向大家介绍Lua中的metatable详解,包括了Lua中的metatable详解的使用技巧和注意事项,需要的朋友参考一下

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

Read-only 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', '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

 类似资料:
  • 本文向大家介绍Lua中的metatable介绍,包括了Lua中的metatable介绍的使用技巧和注意事项,需要的朋友参考一下 setmetatable (table, metatable) Lua 中的每个值都可以用一个 metatable。 这个 metatable 就是一个原始的 Lua table , 它用来定义原始值在特定操作下的行为。 你可以通过在 metatable 中的特定域设一些

  • 主要内容:__index 元方法,实例,__newindex 元方法,实例,实例,为表添加操作符,实例,__call 元方法,实例,__tostring 元方法,实例在 Lua table 中我们可以访问对应的 key 来得到 value 值,但是却无法对两个 table 进行操作(比如相加)。 因此 Lua 提供了元表(Metatable),允许我们改变 table 的行为,每个行为关联了对应的元方法。 例如,使用元表我们可以定义 Lua 如何计算两个 table 的相加操作 a+b。 当 L

  • 在 Lua table 中我们可以访问对应的key来得到value值,但是却无法对两个 table 进行操作。 因此 Lua 提供了元表(Metatable),允许我们改变table的行为,每个行为关联了对应的元方法。 例如,使用元表我们可以定义Lua如何计算两个table的相加操作a+b。 当Lua试图对两个表进行相加时,先检查两者之一是否有元表,之后检查是否有一个叫"__add"的字段,若找到

  • 本文向大家介绍Lua中的__index方法详解,包括了Lua中的__index方法详解的使用技巧和注意事项,需要的朋友参考一下 当我们访问一个表的不存在的域,返回结果为nil,这是正确的,但并不一定正确。实际上,这种访问触发lua解释器去查找__index metamethod:如果不存在,返回结果为nil;如果存在则由__index metamethod返回结果。 这个例子的原型是一种继承。假设

  • 本文向大家介绍Lua中的元方法__newindex详解,包括了Lua中的元方法__newindex详解的使用技巧和注意事项,需要的朋友参考一下 好吧,我写文章的进度已经赶不上看书的进度了,简单的几段文字就够我唠叨一篇文章了。 今天继续来说说元方法,与__index有点相似的__newindex元方法。 1.查询与更新 上一篇文章我们介绍了__index元方法,总结来说,__index元方法是用于处

  • 本文向大家介绍详解Lua中的元表概念,包括了详解Lua中的元表概念的使用技巧和注意事项,需要的朋友参考一下  元表是一个表,有助于改变它连接到一个密钥集和相关的元方法的帮助下表的行为。这些元方法是强大的lua功能,如:     更改/添加功能,以运算符表     查看metatables当钥匙不在使用__index元表中的表可用。 有迹象表明,在处理metatables其中包括使用了两种重要的方法