13.1 算术运算的Metamethods

这一部分我们通过一个简单的例子介绍如何使用metamethods。假定我们使用table来描述结合,使用函数来描述集合的并操作,交集操作,like操作。我们在一个表内定义这些函数,然后使用构造函数创建一个集合:

Set = {}

 

function Set.new (t)

    local set = {}

    for _, l in ipairs(t) do set[l] = true end

    return set

end

 

function Set.union (a,b)

    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

为了帮助理解程序运行结果,我们也定义了打印函数输出结果:

function Set.tostring (set)

    local s = "{"

    local sep = ""

    for e in pairs(set) do

       s = s .. sep .. e

       sep = ", "

    end

    return s .. "}"

end

 

function Set.print (s)

    print(Set.tostring(s))

end

现在我们想加号运算符(+)执行两个集合的并操作,我们将所有集合共享一个metatable,并且为这个metatable添加如何处理相加操作。

第一步,我们定义一个普通的表,用来作为metatable。为避免污染命名空间,我们将其放在set内部。

Set.mt = {}       -- metatable for sets

第二步,修改set.new函数,增加一行,创建表的时候同时指定对应的metatable

function Set.new (t)     -- 2nd version

    local set = {}

    setmetatable(set, Set.mt)

    for _, l in ipairs(t) do set[l] = true end

    return set

end

这样一来,set.new创建的所有的集合都有相同的metatable了:

s1 = Set.new{10, 20, 30, 50}

s2 = Set.new{30, 1}

print(getmetatable(s1))     --> table: 00672B60

print(getmetatable(s2))     --> table: 00672B60

第三步,给metatable增加__add函数。

Set.mt.__add = Set.union

Lua试图对两个集合相加时,将调用这个函数,以两个相加的表作为参数。

通过metamethod,我们可以对两个集合进行相加:

s3 = s1 + s2

Set.print(s3)     --> {1, 10, 20, 30, 50}

同样的我们可以使用相乘运算符来定义集合的交集操作

Set.mt.__mul = Set.intersection

 

Set.print((s1 + s2)*s1)     --> {10, 20, 30, 50}

对于每一个算术运算符,metatable都有对应的域名与其对应,除了__add__mul外,还有__sub()__div()__unm()__pow(),我们也可以定义__concat定义连接行为。

当我们对两个表进行加没有问题,但如果两个操作数有不同的metatable例如:

s = Set.new{1,2,3}

s = s + 8

Lua选择metamethod的原则:如果第一个参数存在带有__add域的metatableLua使用它作为metamethod,和第二个参数无关;

否则第二个参数存在带有__add域的metatableLua使用它作为metamethod 否则报错。

Lua不关心这种混合类型的,如果我们运行上面的s=s+8的例子在Set.union发生错误:

bad argument #1 to `pairs' (table expected, got number)

如果我们想得到更加清楚地错误信息,我们需要自己显式的检查操作数的类型:

function Set.union (a,b)

    if getmetatable(a) ~= Set.mt or

                     getmetatable(b) ~= Set.mt then

       error("attempt to `add' a set with a non-set value", 2)

    end

       ...  -- same as before


相关链接:
lua程序设计目录 - 中国lua开发者 - lua论坛