什么是元表
元表像是一个“操作指南”,里面包含了一系列操作的解决方案,例如__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' ) ) & nbsp;
print ( getmetatable ( 10 ) ) & nbsp; & nbsp;
Metamethod 的参数为操作数(operands),例如:
< div class= "codetitle" > 代码如下: < / div>
< div class= "codebody" id= "code69472" >
< br>
local mt = { } < br>
function mt. __add ( a, b) < br>
& nbsp; & nbsp; & nbsp; 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:
其他的关系操作符都是用上面三种表示: 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) )
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) )
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)
__index元方法 :
很多人对此都有误解,这个误解是:如果A的元表是B,那么如果访问了一个A中不存在的成员,就会访问查找B中有没有这个成员。 而这个理解是完全错误的,实际上,即使将A的元表设置为B,而且B中也确实有这个成员,返回结果仍然会是nil,原因就是B的__index元方法没有赋值。别忘了我们之前说过的:“元表是一个操作指南”,定义了元表,只是有了操作指南,但不应该在操作指南里面去查找元素,而__index方法则是操作指南的索引失败时该怎么办 。这么说有点绕。所以:
举个栗子
father = {
house= 1
}
son = {
car= 1
}
setmetatable ( son, father)
print ( son. house)
输出的结果是nil,但如果把代码改为
father = {
house= 1
}
father. __index = father
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方法是一个函数,则返回该函数的返回值