算术类的元方法
对于每一个算术运算符,metatable
都有对应的域名与其对应,除了__add
、__mul
外,还有__sub
(减)、__div
(除)、__unm
(负)、__pow
(幂),我们也可以定义__concat
定义连接行为。
当我们对两个表进行加没有问题,但如果两个操作数有不同的metatable例如:
s = Set.new{1,2,3}
s = s + 8
Lua选择metamethod
的原则:如果第一个参数存在带有__add
域的metatable
,Lua使用它作为metamethod
,和第二个参数无关;
否则第二个参数存在带有__add
域的metatable
,Lua使用它作为metamethod
否则报错。
关系类的元方法
元表也允许我们指定操作符的含义:__eq
(等于),__lt
(小于),和__le
(小于等于)给关系运算符赋予特殊的含义。对剩下的三个关系运算符没有专门的元方法,因为Lua将a ~= b
转换为not (a == b)
;a > b
转换为b < a
;a >= b
转换为 b <= a
。
我们将__le
和__lt
的实现分开:
Set.mt.__le = function (a,b) -- set containment
for k in pairs(a) do
if not b[k] then return false end
end
return true
end
-- 我们通过集合的包含来定义集合相等:
Set.mt.__lt = function (a,b)
return a <= b and not (b <= a)
end
Set.mt.__eq = function (a,b)
return a <= b and b <= a
end
-- 有了上面的定义之后,现在我们就可以来比较集合了:
s1 = Set.new{2, 4}
s2 = Set.new{4, 10, 2}
print(s1 <= s2) --> true
print(s1 < s2) --> true
print(s1 >= s1) --> true
print(s1 > s1) --> false
print(s1 == s2 * s1) --> true
库定义的元方法
tostring
以简单的格式表示出table:
print({}) --> table: 0x8062ac0
(注意:print
函数总是调用tostring
来格式化它的输出)。然而当格式化一个对象的时候,tostring
会首先检查对象是否存在一个带有__tostring
域的metatable
。如果存在则以对象作为参数调用对应的函数来完成格式化,返回的结果即为tostring
的结果。
在我们集合的例子中我们已经定义了一个函数来将集合转换成字符串打印出来。因此,我们只需要将集合的`metatable
的__tostring
域调用我们定义的打印函数:
Set.mt.__tostring = Set.tostring
这样,不管什么时候我们调用print
打印一个集合,print
都会自动调用tostring
,而tostring
则会调用Set.tostring
:
s1 = Set.new{10, 4, 5}
print(s1) --> {4, 5, 10}
table访问的元方法
__index
当我们访问一个表的不存在的域,返回结果为nil
,这是正确的,但并不一定正确。实际上,这种访问触发lua解释器去查找__index
元方法:如果不存在,返回结果为nil
;如果存在则由__index
元方法返回结果。
当我们想不通过调用__index
元方法来访问一个表,我们可以使用rawget
函数。Rawget(t,i)
的调用以raw access
方式访问表。这种访问方式不会使你的代码变快(the overhead of a function call kills any gain you could have),但有些时候我们需要他,在后面我们将会看到。
__newindex
__newindex
元方法用来对表更新,__index
则用来对表访问。当你给表的一个缺少的域赋值,解释器就会查找__newindex
元方法:如果存在则调用这个函数而不进行赋值操作。像__index
一样,如果元方法是一个表,解释器对指定的那个表,而不是原始的表进行赋值操作。另外,有一个raw
函数可以绕过元方法:调用rawset(t,k,v)
不掉用任何元方法对表t的k域赋值为v。__index
和__newindex
元方法的混合使用提供了强大的结构:从只读表到面向对象编程的带有继承默认值的表。
示例
-- Point.lua
local point = {}
local mt = {}
--重定义+运算
function mt.__add(p1, p2)
return point.new(p1.x + p2.x, p1.y + p2.y, p1.z + p2.z)
end
--重定义-运算
function mt.__sub(p1, p2)
return point.new(p1.x - p2.x, p1.y - p2.y, p1.z - p2.z)
end
--重定义==运算
function mt.__eq(p1, p2)
return p1.x == p2.x and p1.y == p2.y and p1.z == p2.z
end
--返回包含 x 和 y 坐标的值的字符串
function mt.__tostring(p)
return string.format("[Point (%f, %f, %f)]", p.x, p.y, p.z)
end
---------------------------------------------------------------
--创建一个新点
function point.new(x, y, z)
local p = {x=0, y=0, z=0}
setmetatable(p, mt)
if type(x) == "number" then p.x = x end
if type(y) == "number" then p.y = y end
if type(z) == "number" then p.z = z end
return p
end
--创建此 Point 对象的副本
function point.clone(p)
return point.new(p.x, p.y, p.z)
end
--[静态] 返回 p1 和 p2 之间的距离
function point.Distance2D(p1, p2)
return math.sqrt((p1.x - p2.x)^2 + (p1.z - p2.z)^2)
end
--[静态] 返回 p1 和 p2 之间的距离的平方
function point.Distance2DSquare(p1, p2)
return (p1.x - p2.x)^2 + (p1.z - p2.z)^2
end
--[静态] 返回 p1 和 p2 之间的距离
function point.Distance(p1, p2)
return math.sqrt((p1.x - p2.x)^2 + (p1.y - p2.y)^2 + (p1.z - p2.z)^2)
end
return point