可以通过元表来修改一个值的行为,使其在面对一个非预定义的操作时执行一个指定的操作。当Lua试图将两个table相加时,它会先检查两者之一是否有元表,然后检查该原表中是否有一个叫__add的字段。
Lua在创建新的table时不会创建元表,可以使用setmetatable来设置或修改任何table的元表。
在Lua中,只能设置table的元表,若要设置其他类型的值的元表,则必须通过C代码来完成。其他类型在默认情况下都没有元表。
1、算术类的元方法
Set = {} local mt = {} --根据参数列表中的值创建一个新的集合 function Set.new( l ) local set = {} setmetatable(set, mt) --将mt设置为当前所创建table的元表 for _, v in ipairs(l) do set[v] = 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 l = {} for e in pairs(set) do l[#l + 1] = e end return "{" ..table.concat(l, ", ") .. "}" --table.concat函数会将给定列表中的所有字符串连接起来,并返回连接结果 end function Set.print( s ) print(Set.tostring(s)) end s1 = Set.new{1, 2, 3, 4} s2 = Set.new{2, 5} --两个集合具有一个相同的元表 print(getmetatable(s1)) -->table: 0x7fee11c09940 print(getmetatable(s2)) -->table: 0x7fee11c09940 --将元方法加入元表中。 mt.__add = Set.union --当Lua试图将两个集合相加时,就会调用Set.union函数,并将两个操作数作为参数传入 s3 = s1 + s2 Set.print(s3) -->{1, 2, 3, 4, 5} --类似的,还可以使用乘号来求集合的交集 mt.__mul = Set.intersection Set.print((s1 + s2) * s1) -->{1, 2, 3, 4} --[[ 在元表中,每种算术操作符都有对应的字段名。除了__add 和__mul外,还有__sub(减法)、 __div(除法)、__unm(相反数)、__mod(取模)、__pow(乘幂)。此外,还可以定义__concat 字段,用于描述连接操作符的行为。 ]]
2、关系类的元方法
mt.__le = function ( a, b ) --集合包含 for k in pairs(a) do if not b[k] then return false end end return true end mt.__lt = function ( a, b ) return a <= b and not (b <= a) end mt.__eq = function ( a, b ) return a <= b and b <= a end s1 = Set.new{2, 4} s2 = Set.new{2, 4, 8} print(s1 <= s2) -->true print(s1 < s2) -->true print(s1 >= s2) -->false print(s1 > s2) -->false print(s1 == s2 * s1) -->true
3、table访问的元方法
--[[当访问一个table中不存在的字段时,会促使解释器去查找一个叫__index的元方法。 如果没有这个元方法,那么访问结果为nil,否则就由这个元方法来提供最终结果。 ]] Window = {} --创建一个名字空间 --使用默认值来创建一个原型 Window.prototype = {x=0, y=0, width=100, height=200} Window.mt = {} --创建元表 --声明构造函数 function Window.new( o ) setmetatable(o, Window.mt) return o end --定义__index元方法 Window.mt.__index = function ( table, key ) return Window.prototype[key] end --__index不必是一个函数,还可以是一个table --Window.mt.__index = Window.prototype --创建一个新窗口,并查询一个它没有的字段 w = Window.new{x = 10, y = 20} print(w.width) -->100
__newindex元方法与__index元方法类似,不同之处在于前者用于table的更新,而后者用于table的查询。当对一个table中不存在的索引赋值时,解释器就会查找__newindex元方法。如果有这个元方法,解释器就调用它,而不是执行赋值。