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》