lua中的元表---metatable
Posted Flytiger1220
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了lua中的元表---metatable相关的知识,希望对你有一定的参考价值。
什么是元表
元表像是一个“操作指南”,里面包含了一系列操作的解决方案,例如__index方法就是定义了这个表在索引失败的情况下该怎么办。
Lua 中 metatable 是一个普通的 table,但其主要有以下几个功能:
1.定义算术操作符和关系操作符的行为
2.为 Lua 函数库提供支持
3.控制对 table 的访问
1、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<br>
print(getmetatable(10)) --> nil</div>
Metamethod 的参数为操作数(operands),例如:
<div class="codetitle"> 代码如下:</div>
<div class="codebody" id="code69472">
<br>
local mt = <br>
function mt.__add(a, b)<br>
return 'table + ' .. b<br>
end<br>
local t = <br>
setmetatable(t, mt)<br>
print(t + 1)</div>
每个算术操作符有对应的 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。
2、为 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
3、控制 table 的访问
__index metamethod
在我们访问 table 的不存在的域时,Lua 会尝试调用 __index metamethod。__index metamethod 接受两个参数 table 和 key:
代码如下:local mt =
mt.__index = function(table, key)
print('table -- ' .. tostring(table)) -->table -- table: 0x149f6f0
print('key -- ' .. key) -->key -- a
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元方法:
很多人对此都有误解,这个误解是:如果A的元表是B,那么如果访问了一个A中不存在的成员,就会访问查找B中有没有这个成员。
而这个理解是完全错误的,实际上,即使将A的元表设置为B,而且B中也确实有这个成员,返回结果仍然会是nil,原因就是B的__index元方法没有赋值。别忘了我们之前说过的:“元表是一个操作指南”,定义了元表,只是有了操作指南,但不应该在操作指南里面去查找元素,而__index方法则是操作指南的索引失败时该怎么办。这么说有点绕。所以:
举个栗子
father =
house=1
son =
car=1
setmetatable(son, father) --把son的metatable设置为father
print(son.house)
输出的结果是nil,但如果把代码改为
father =
house=1
father.__index = father -- 把father的__index方法指向自己
son =
car=1
setmetatable(son, father)
print(son.house)
输出的结果为1,符合预期
这样一来,结合上例,来解释__index元方法的含义:
在上述例子中,访问son.house时,son中没有house这个成员,但Lua接着发现son有元表father,注意:此时,Lua并不是直接在father中找名为house的成员,而是调用father的__index方法,如果__index方法为nil,则返回nil,如果是一个表(上例中father的__index方法等于自己,就是这种情况),那么就到__index方法所指的这个表中查找名为house的成员,于是,最终找到了house成员。
注:__index方法除了可以是一个表,还可以是一个函数,如果是一个函数,__index方法被调用时将返回该函数的返回值。
到这里,总结一下Lua查找一个表元素时的规则,其实就是如下3个步骤:
1.在表中查找,如果找到,返回该元素,找不到则继续
2.判断该表是否有元表(操作指南),如果没有元表,返回nil,有元表则继续
3.判断元表(操作指南)中有没有关于索引失败的指南(即__index方法),如果没有(即__index方法为nil),则返回nil;如果__index方法是一个表,则重复1、2、3;如果__index方法是一个函数,则返回该函数的返回值
以上是关于lua中的元表---metatable的主要内容,如果未能解决你的问题,请参考以下文章