学习lua也有大概一年了,对lua的一些基本的语法很熟练了,也做了一些简单的业务,但是对于lua的高级特性还是不是很熟,最近有时间得以系统的学习学习。本文主要讲述的是lua高级特性之一的元表和元方法。
文字简述
metatable(元表) 本质上来讲元表也是一个表,不过这个表是用来定义对lua的值进行自定义运算行为的地方。
metamethod(元方法) 本质上来讲就是一个lua函数,不过这个函数是用来绑定lua中特定的值,这些特定的值可以称为事件。这个函数我们可以进行我们一些自定义的操作。
元表之中的事件其实是一些定义的值,这些值后面会讲到;
实际上我们只能对lua中table类型的值进行修改元表和元方法的操作,其它的一些例如number, string等都已经有自己内置的元表和元方法,且不可改变。通过元表和元方法,我们可以实现lua的面向对象编程。
代码讲解
api 介绍
简单的介绍一下会用到的api。
setmetatable(table, metatable)
设置table的元表为metatable并且返回这个table。不能为除table类型之外的值设置元表,如果metatable为nil,则将指定的元表移除 。如果存在__metatable
,则会抛出一个错误。getmetatable(obj)
返回一个类型的元表,如果没有元表返回nil。如果存在__metatable
,则返回这个域的值。rawget(table, index)
在不触发任何元方法的情况下获取table中的值。也就是跳过元表和元方法。rawset(table, index, value)
在不触发任何元方法的情况下设置table[index]的值为value,index不能是nil和NaN
元方法介绍
我们都知道对于两个number型的值,我们可以进行加,减,乘,除等的元算,但是对于table我们是不能直接进行这些预定义的运算的。但是通过通过元表和元方法我们是可以实现的;首先介绍下有哪些特定的值被用于绑定元方法,也称为事件,如下:
1 | __index -- 用于取操作 |
对于不同的lua版本可能这些事件还有区别,具体详细的可以看lua对应版本的介绍,这里只列出了一些常用的。
对于一些特定的事件进行一些简单的介绍
- __index 当我们在取一个table中的不存在这个index的值的时候,如果有元表的话,会触发这个操作,会到元表中进行查询,并且返回这个值,元表中月不存在的时候返回nil。
- __newindex 当我们对一个table中的一个不存在的index赋值的时候,如果有元表的话,会触发这个操作,如果元表中有定义这个行为,就按照这个进行。
- __metatable 使用这个元方法的时候是保护元表,进值对元表中的成员进行获取或者修改
- __call 使用这个的时候我们可以吧table当成函数来进行调用。
代码分析
简单的元方法
1 | local t1 = {1, 2, 3} |
运行结果如下
1 | t1 : 1|2|3 |
当对两个table进行加(+)的操作的时候,会查找元表中对应的元方法,然后按照元方法的行为去做。其它的一些算术运算都和这个例子大同小异,就不多做介绍了。
__index
1 | local t1 = {} |
运行结果如下
1 | 10 |
当访问t1中的a的时候,t1中并没有这个值,但是t1有元表,则会到元表中查询a,并返回;
__index 也可以是一个函数,用于自定义的一些行为。
__newindex
1 | local t1 = {} |
运行结果如下
1 | nil |
在对t1中的变量进行赋值的时候,如果存在则直接进行赋值,如果不存在则触发__newindex,设置元表中对应的值
__metatable
1 | local t1 = {} |
运行结果如下
1 | table: 0xe7f4f0 |
在设置完__metatable域的时候,就不能再对元表进行操作了,会报错。
__call
1 | local t1 = {} |
运行结果如下
1 | __call str : 6 |
t1作为table,但是可以直接当成函数来进行调用,会查找__call元方法
rawget
1 | local t1 = {} |
运行结果如下
1 | t1 a : 20 |
设置完元表后可以取到t1中的a,从元表t2中,但是用rawget的时候会会忽略元表的存在
rawset
1 | local t1 = {} |
运行结果如下
1 | t1 b : nil |
正常的设置完元表并且设置__newindex域之后,对t1中的不存在的b赋值的时候会触发__newindex操作,但是如果用rawset的话就会报错,rawset(t1, b, “ccc”),会对t1中的b进行赋值,并不会触发__newindex,而t1中也没有b这个值,所以报错了。
系统代码
以一个之前写的例子结束这篇介绍
1 | --[[ |
这是仿照lua-resty-redis,用luasocket实现的一个简单的lua redis客户端。
每次用的时候需要 require这个文件,并且调用new,设置相应元表,之后就可以进行简单的redis操作了。
后记
- 本文代码部分比较多,尽可能的用代码来解释lua中元表和元方法的一些用法,如果理解起来还是不清楚可以查看lua官方文档,也可以联系我。
* 如有疑问欢迎批评指正,谢谢! *