Lua元表与元方法
前言元表对应的英文是metatable,元方法是metamethod。我们都知道,在C++中,两个类是无法直接相加的,但是,如果你重载了“+”符号,就可以进行类的加法运算。在Lua中也有这个道理,两个table类型的变量,你是无法直接进行“+”操作的,如果你定义了一个指定的函数,就可以进行了。 Lua是怎么做的?通常,Lua中的每个类型的值都有一套预定义的操作集合,比如数字是可以相加的,字符串是可以连接的,但是对于两个table类型,则不能直接进行“+”操作。这需要我们进行一些操作。在Lua中有一个元表(metastable),我们可以通过元表来修改一个值得行为,使其在面对一个非预定义的操作时执行一个指定的操作。比如,现在有两个table类型的变量a和b,我们可以通过metatable定义如何计算表达式a+b,具体的在Lua中是按照以下步骤进行的:
上述四个步骤就是计算table类型变量a+b的过程。在Lua中,每个值都有一个元表,table和userdata类型的每个变量都可以有各自独立的元表,而其他类型的值则共享其类型所属的单一元表。任何一个表都可以是其他一个表的metatable,一组相关的表可以共享一个metatable(描述他们共同的行为)。一个表也可以是自身的metatable(描述其私有行为)。 算术运算的元方法Set = {} --对于集合的操作有,取并集和取交集; 对于两个集合的关系有,是否包含,是否相等 Set.mt = {} --集合的元表 --根据参数列表中的值创建一个新的集合 function Set.new(t) local set = {} setmetatable(set,Set.mt) --Set.new创建的所有集合都有相同的metatable for _,v in pairs(t) do set[v] = true end return set end --并集操作 function Set.union(a,b) local res = Set.new{} --相当于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(t) local tb = {} for k in pairs(t) do tb[#tb + 1] = k end return "{" .. table.concat(tb,",") .. "}" end function Set.print(t) print(Set.tostring(t)) end local s1 = Set.new({10,20,30,50}) local s2 = Set.new({30,1}) print(getmetatable(s1)) -->table: 03B90CD0 print(getmetatable(s2)) -->table: 03B90CD0,说明s1与s2有相同的元表 Set.mt.__add = Set.union --给metatable增加__add函数,来去两个集合的全集 Set.print(s1 + s2) -->{1,10,50,20} Set.mt.__mul = Set.intersection --给metatable增加__mul函数,来去两个集合的交集 Set.print((s1 + s2)*s1) -->{30,20} 对于每一个算术运算符,metatable都有对应的域名与其对应,除了__add(加)、__mul(乘)外,还有__sub(减)、__div(除)、__unm(负)、__pow(幂),我们也可以定义__concat定义连接行为。
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 local res = Set.new{} --相当于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 关系运算的元方法Metatables也允许我们使用metamethods:__eq(等于),__lt(小于),和__le(小于等于)给关系运算符赋予特殊的含义。对剩下的三个关系运算符没有专门的metamethod,因为Lua将a~= b转换为not (a == b);a > b转换为b < a;a >= b转换为 b <= a。 (直到Lua 4.0为止,所有的比较运算符被转换成一个,a <= b转为not (b < a)。然而这种转换并不一致正确。当我们遇到偏序(partialorder)情况,也就是说,并不是所有的元素都可以正确的被排序情况。例如,在大多数机器上浮点数不能被排序,因为他的值不是一个数字(Not a Number即NaN)。根据IEEE754的标准,NaN表示一个未定义的值,比如0/0的结果。该标准指出任何涉及到NaN比较的结果都应为false。也就是说,NaN <= x总是false,x< NaN也总是false。这样一来,在这种情况下a<= b 转换为 not (b < a)就不再正确了。) 在我们关于集合操作的例子中,有类似的问题存在。<=代表集合的包含:a <= b表示集合a是集合b的子集。这种意义下,可能a<= b和b < a都是false;因此,我们需要将__le和__lt的实现分开:? --判断两个集合的包含关系(小于等于)--子集关系 Set.mt.__le = function (a,b) 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 local s4 = Set.new({2,4}) local s5 = Set.new({4,2}) print(s4 <= s5) --true print(s4 < s5) --true print(s4 >= s4) --true print(s4 > s4) --false print(s4 == s4 * s5) --true 与算术运算的metamethods不同,关系元算的metamethods不支持混合类型运算。对于混合类型比较运算的处理方法和Lua的公共行为类似。如果你试图比较一个字符串和一个数字,Lua将抛出错误。相似的,如果你试图比较两个带有不同metamethods的对象,Lua也将抛出错误。 但相等比较从来不会抛出错误,如果两个对象有不同的metamethod,比较的结果为false,甚至可能不会调用metamethod。这也是模仿了Lua的公共的行为,因为Lua总是认为字符串和数字是不等的,而不去判断它们的值。仅当两个有共同的metamethod的对象进行相等比较的时候,Lua才会调用对应的metamethod。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |