lua中提供的元表(metatable)与元方法(metamethod)是一种非常重要的语法,metatable主要用于做一些类似于C++重载操作符式的功能。元表与元方法会影响table的访问行为。
元表的由来
Lua2.1灵活语义问世,极大的增加了Lua的表达能力,从此,灵活语义就变成了Lua的标志。
灵活语义的一个目标是允许table作为对象和类的基础。为了实现这个目标,需要实现table的继承。另一个目标是将userdata变成应用数据的天然代理,可以作为函数参数而不只是一个句柄。userdata希望能够索引,就好像他们只是一个table,可供调用他们身上的方法。所以fallback机制的实现,让Lua把未定义行为交给程序员处理,而不是直接在语言本身实现这些特性。
Lua2.1提供了fallback机制,支持以下行为:table索引,算术操作符,字符串拼接,顺序比较,函数调用。当这些操作应用到“错误”的类型上,对应的fallback就会被调用到,允许程序员决定Lua如何处理。table索引fallback允许userdata和其它值类型表现的跟表一样。定义当Key不在table时的fallback,从而实现多种形式的继承(通过委托)。为了完善面向对象编程,添加了两个语法糖:function a:foo(…)就好比function a.foo(self,…)一样,以及a:foo(…)作为a.foo(a, …)的语法糖。
Lua2.1里引入的fallback机制,可以很好的支持灵活扩展的语义,但这个机制是全局的:每个事件只有一个钩子。这让共享或重用代码变的很艰难,因为同一事件的fallback在模块里只能定义一次,不能共存。Lua 3.0解决了fallback冲突问题。fallback替换为tag方法:钩子是以(event, tag)的形式挂在字典里的。Tags是在Lua2.1引入的整数标签,可以附在userdata上。最初的动机是希望同类的C对象,在Lua里都有相同的tag(不过,Lua没有强迫要对Tag提供解释)。Lua3.0里对所有值类型提供了tag支持,以支持对应的tag方法。
标记方法机制工作的很好,一直存续到Lua 5.0为止。在Lua 5.0实现了元表和元方法来取代标记和标记方法。元表只是普通的Lua table,所以可以用Lua直接操作,不需要特殊函数。就像标记一样,元表可以用来表示userdata和table的用户定义类型:所有“同类”对象应该共享同一个元表。不像标记,元表和他们的内容会在所有引用消失后自动被回收掉。(相反,标记和标记方法会等到程序结束才会被回收。)元表的引入同时简化了实现:标记方法需要在Lua核心代码里添加特殊的私有表示方法,元表主要就是标准的table机制。
下面的代码展示了Lua 5.0里,继承是如何实现的。index元方法取代了index标记,元表里则是用__index域来表示。代码通过将b的元表里的__index域指向a,实现了b继承a。(一般情况下,index元方法都是函数,但允许它设为table,以直接支持简单的委托继承。)
1 | a=Window{x=100, y=200, color="red"} |
元方法
在Lua中有一个元表,也就是上面说的metatable,我们可以通过元表来修改一个值得行为,使其在面对一个非预定义的操作时执行一个指定的操作。比如,现在有两个table类型的变量a和b,我们可以通过metatable定义如何计算表达式a+b。
我们是使用getmetatable来获取一个table或userdata类型变量的元表,当创建新的table变量时,使用getmetatable去获得元表,将返回nil;同理,我们也可以使用setmetatable去设置一个table或userdata类型变量的元表。
在table中,我可以重新定义的元方法有以下几个:
1 | __add(a, b) --加法 |
元表的实现
reference
《The evolution of Lua》