前言
元表对应的英文是metatable,元方法是metamethod。我们都知道,在C++中,两个类是无法直接相加的,但是,如果你重载了“+”符号,就可以进行类的加法运算。在Lua中也有这个道理,两个table类型的变量,你是无法直接进行“+”操作的,如果你定义了一个指定的函数,就可以进行了。那这篇博文就是主要讲的如何定义这个指定的函数,这个指定的函数是什么?希望对学习Lua的朋友有帮助。
Lua是怎么做的?
通常,Lua中的每个值都有一套预定义的操作集合,比如数字是可以相加的,字符串是可以连接的,但是对于两个table类型,则不能直接进行“+”操作。这需要我们进行一些操作。在Lua中有一个元表,也就是上面说的metatable,我们可以通过元表来修改一个值得行为,使其在面对一个非预定义的操作时执行一个指定的操作。比如,现在有两个table类型的变量a和b,我们可以通过metatable定义如何计算表达式a+b,具体的在Lua中是按照以下步骤进行的:
- 先判断a和b两者之一是否有元表;
- 检查该元表中是否有一个叫__add的字段;
- 如果找到了该字段,就调用该字段对应的值,这个值对应的是一个metamethod;(Lua中方法是可以放在一个字段中的,还记得???忘了点)
- 调用__add对应的metamethod计算a和b的值。
上述四个步骤就是计算table类型变量a+b的过程。在Lua中,每个值都有一个元表,table和userdata类型的每个变量都可以有各自独立的元表,而其他类型的值则共享其类型所属的单一元表。
告别metatable小白
现在就说说最基本的metatable内容。Lua在创建新的table时不会创建元表,比如以下代码就可以演示:
local t = { 1, 2} print(getmetatable(t)) -- nil
我们是使用getmetatable来获取一个table或userdata类型变量的元表,当创建新的table变量时,使用getmetatable去获得元表,将返回nil;同理,我们也可以使用setmetatable去设置一个table或userdata类型变量的元表,例如以下代码:
local t = {} print(getmetatable(t)) -->nil local t1 = {} setmetatable(t, t1) assert(getmetatable(t) == t1)
任何table都可以作为任何值得元表,而一组相关的table有可以共享一个通用的元表,此元表描述了它们共同的行为。一个table甚至可以作为它自己的元表,用于描述其特有的行为。总之,任何搭配形式都是合法的。
在Lua代码中,只能设置table的元表。若要设置其它类型的值得元表,则必须通过C代码来完成。还存在一个特例,对于字符串,标准的字符串程序库为所有的字符串都设置了一个元表,而其它类型在默认情况下都没有元表。查看两句代码的打印值,就可以看出来:
print(getmetatable("Hello World")) print(getmetatable(10))
在table中,我可以重新定义的元方法有以下几个:
__add(a, b) --加法 __sub(a, b) --减法 __mul(a, b) --乘法 __div(a, b) --除法 __mod(a, b) --取模 __pow(a, b) --乘幂 __unm(a) --相反数 __concat(a, b) --连接 __len(a) --长度 __eq(a, b) --相等 __lt(a, b) --小于 __le(a, b) --小于等于 __index(a, b) --索引查询 __newindex(a, b, c) --索引更新(PS:不懂的话,后面会有讲) __call(a, ...) --执行方法调用 __tostring(a) --字符串输出 __metatable --保护元表
接下来就介绍介绍如果去重新定义这些方法。
算术类的元方法
现在我使用完整的实例代码来详细的说明算术类元方法的使用。我准备定义一些对集合的操作方法,所有的方法都放入Set这个table中,至于为什么table中可以存放函数,可以参考这篇文章。下面的代码是我模拟的一个集合的操作:
Set = {} local mt = {} -- 集合的元表 -- 根据参数列表中的值创建一个新的集合 function Set.new(l) local set = {} setmetatable(set, mt) for _, v in pairs(l) do set[v] = true end return set end -- 并集操作 function Set.union(a, b) local retSet = Set.new{} -- 此处相当于Set.new({}) for v in pairs(a) do retSet[v] = true end for v in pairs(b) do retSet[v] = true end return retSet end -- 交集操作 function Set.intersection(a, b) local retSet = Set.new{} for v in pairs(a) do retSet[v] = b[v] end return retSet end -- 打印集合的操作 function Set.toString(set) local tb = {} for e in pairs(set) do tb[#tb + 1] = e end return "{" .. table.concat(tb, ", ") ..