LUA元表、元操作

  


1.元表、元操作

1.1算术元操作

Metatables允许我们改变table的行为,例如,使用Metatables我们可以定义Lua如何计算两个table的相加操作a+b。当Lua试图对两个表进行相加时,他会检查两个表是否有一个表有Metatable,并且检查Metatable是否有__add域。如果找到则调用这个__add函数(所谓的Metamethod)去计算结果。


Lua中的每一个表都有其Metatable。(后面我们将看到userdata也有Metatable),Lua默认创建一个不带metatable的新表

t = {}

print(getmetatable(t))      --> nil


任何一个表都可以是其他一个表的 metatable ,一组相关的表可以共享一个 metatable (描述他们共同的行为)。一个表也可以是自身的 metatable (描述其私有行为)。

Set.mt = {}       -- metatable for sets

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

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。为避免污染命名空间,我们将其放在set内部。

Set.mt = {}       -- metatable for sets

第二步,给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}

除了 __add __mul 外,还有 __sub( ) __div( ) __unm( ) __pow( ) ,我们也可以定义 __concat 定义连接行为。

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

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

1.2关系元操作

Metatables也允许我们使用metamethods__eq(等于),__lt(小于),和__le(小于等于)给关系运算符赋予特殊的含义

对剩下的三个关系运算符没有专门的metamethod,因为Luaa ~= b转换为not (a == b)a > b转换为b < aa >= b转换为 b <= a

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

与算术运算的 metamethods 不同,关系元算的 metamethods 不支持混合类型运算。对于混合类型比较运算的处理方法和 Lua 的公共行为类似。

1.3库定义元操作

注意:print函数总是调用tostring来格式化它的输出)。然而当格式化一个对象的时候,tostring会首先检查对象是否存在一个带有__tostring域的metatable。如果存在则以对象作为参数调用对应的函数来完成格式化,返回的结果即为tostring的结果。
Set.mt.__tostring = Set.tostring

setmetatable/getmetatable函数也会使用metafield,在这种情况下,可以保护metatables。假定你想保护你的集合使其使用者既看不到也不能修改metatables。如果你对metatable设置了__metatable的值,getmetatable将返回这个域的值,而调用setmetatable 将会出错:

Set.mt.__metatable = "not your business"

 

s1 = Set.new{}

print(getmetatable(s1))     --> not your business

setmetatable(s1, {})

stdin:1: cannot change protected metatable

1.4有默认的表值元操作

在一个普通的表中任何域的默认值都是nil。很容易通过metatables来改变默认值:

function setDefault (t, d)

    local mt = {__index = function () return d end}

    setmetatable(t, mt)

end

 

tab = {x=10, y=20}

print(tab.x, tab.z)      --> 10   nil

setDefault(tab, 0)

print(tab.x, tab.z)      --> 10   0

1.5监控表

我们只要将每一个proxy和他原始的表关联,所有的proxy共享一个公用的metatable即可。将表和对应的proxy关联的一个简单的方法是将原始的表作为proxy的域,只要我们保证这个域不用作其他用途。一个简单的保证它不被作他用的方法是创建一个私有的没有他人可以访问的key。将上面的思想汇总,最终的结果如下:

-- create private index

local index = {}

 

-- create metatable

local mt = {

    __index = function (t,k)

       print("*access to element " .. tostring(k))

       return t[index][k]   -- access the original table

    end

 

    __newindex = function (t,k,v)

    print("*update of element " .. tostring(k) .. " to "

                  .. tostring(v))

    t[index][k] = v          -- update original table

    end

}

 

function track (t)

    local proxy = {}

    proxy[index] = t

    setmetatable(proxy, mt)

    return proxy

end

现在,不管什么时候我们想监控表t,我们要做得只是t=track(t)。

1.6只读表

采用代理的思想很容易实现一个只读表。我们需要做得只是当我们监控到企图修改表时候抛出错误。通过__index metamethod,我们可以不使用函数而是用原始表本身来使用表,因为我们不需要监控查寻。这是比较简单并且高效的重定向所有查询到原始表的方法。但是,这种用法要求每一个只读代理有一个单独的新的metatable,使用__index指向原始表:

function readOnly (t)

    local proxy = {}

    local mt = {         -- create metatable

       __index = t,

       __newindex = function (t,k,v)

           error("attempt to update a read-only table", 2)

       end

    }

 

    setmetatable(proxy, mt)

    return proxy

end


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值