Lua 元表与元方法

算术类的元方法

对于每一个算术运算符,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 < aa >= 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

猜你喜欢

转载自blog.csdn.net/jingangxin666/article/details/80382748
今日推荐