非常非常详细的Lua面向对象(一)——元表与元方法
前言
新的项目终于准备要真正开始使用Lua进行开发了,所以笔者最近也开始了对Lua的学习,从以前的强类型的静态语言C#,到动态脚本语言,多少会有一点点不习惯。除此之外,思考方式也会有细微的差异,毕竟Lua里并没有类这个概念。所以在接下来的几篇文章中,我会尝试将Lua是如何模拟类这一过程梳理清楚,毕竟是刚刚开始,很多感受比较直观以及浮于表面,如果有什么错误疏漏之处,也希望各位能够指出~
1.Lua中的table
如果说在python中一切皆对象的话,那么Lua可以说是一切皆table了,table是是Lua中极其强大的数据结构,一个模块,一个数组,一个字典,他们都可以用table实现,我们模拟面向对象编程,table也是必不可少的,我们的每一个对象其实也就是一个table。关于luatable的介绍网上有非常多,这里就不展开细说了~
2.元表
当我第一次接触到Lua元表的概念时,我脑海里就一个想法“原来表还能够这么玩”。为什么需要出现元表这个概念,因为Lua的表实在是太万能了,对单一表操作再某些场景下很难满足我们的需求,所以元表的出现,可以让我们高度的自定义两个表之间的操作。
2.1设置元表
设置元表非常的简单,如下代码所示,在下文中,我将setmetatable的第一个参数所表示的表称为普通表
,第二个参数所表示的表为元表
mytable = {}--普通表
mymetatable = {}--元表
setmetatable(mytable , mymetatable)--将mymetatable设置为mytable的元表
这一段代码等价于
mymetatable = {}
mytable = setmetatable({},mymetatable)
setmetatable()为Lua的提供的内置方法,其返回值是普通表的引用。
3.元方法
在设置了元表后,我们需要怎么操作才能发挥元表的作用呢,这就不得不提到元方法了。我们首先介绍两个使用频率最高的元方法__index
以及__newindex
。我们使用一种数据结构来存储数据,使用时无外乎两种基本操作(元其实就是基本的意思),读和写,对于table来说也就是索引操作
和赋值操作
3.1__index
如果我们想自定义对一张普通表索引时的一些特殊行为,我们可以通过为其元表添加__index
这个key来实现自定义(注意,是在元表中添加__index
而不是直接设置__index
)。设置的方式非常简单,我们继续使用2.1中的mytable
作为例子。
3.1.1指向table的情况
mymetatable = { __ index = { key2 = "value2" }}
mytable = setmetatable({},mymetatable)
--等价于以下代码,所以希望大家以后看到网上各种写法时不再感到疑惑
mytable = setmetatable({},{ __ index = { key2 = "value2" })
上述代码就在mymetatable
这个表中设置了__index
这个key,而这个key指向的是一个table。
在
__index
指向的是一个table的情况下,对普通表进行索引操作时
- 若普通表存在该key则返回该key所指向的value
- 若普通表不存在该key,则尝试查找该普通表的元表,如果元表中没有
__index
则返回nil,如果有则继续对__index
所指向的表进行索引。- 若该key存在,则返回该key所指向的value
- 若该key不存在,则返回nil
举个栗子,我们依然用上面的代码,只不过在普通表中增加一个key
mymetatable = { __ index = { key2 = "value2" }}
mytable = setmetatable({ key1 = "value1" },mymetatable)
--普通表存在key1,所以返回value1
print(mytable.key1) --输出value1
--普通表不存在key2,__index指向的table中存在,所以返回value2
print(mytable.key2) --输出value2
--普通表和元表都不存在key3,所以返回nil
print(mytable.key3) --输出nil
3.1.2指向function的情况
mytable = setmetatable({}, { __index = function()
print("你正在尝试索引mytable中没有的key")
end}
)
returnValue = mytable.key1 --输出 "你正在尝试索引mytable中没有的key"
print(returnValue) --输出 nil
__index
还可以指向一个function
当我们试图索引一个普通表中不存在的key时,如果元表中存在__index,且__index指向的是一个function,那么就会去执行这个function,索引所得到的值则是该function的返回值(上面例子代码无返回值所以输出为nil)
--存在返回值的情况
mytable = setmetatable({}, { __index = function()
print("你正在尝试索引mytable中没有的key")
return 58
end}
)
returnValue = mytable.key1 --输出 "你正在尝试索引mytable中没有的key"
print(returnValue) --输出 58
3.2__newindex
与索引相对应的,如果我们想自定义对一个普通表的赋值操作,就可以使用__newindex
元方法。其使用逻辑与__index
基本相似,一样是存在指向table,或者指向function的区别。
3.2.1指向table
--指向table
mymetatable = { key2 = "value2" }
mytable = setmetatable({key1="value1"}, { __newindex =mymetatable})
print(mytable.key1) --输出 value1
mytable.key1="modified1"
print(mytable.key1) --输出modified1
mytable.key2="modified2"
print(mytable.key2) --输出nil
我们首先尝试获取mytable
中的key1,然后对其进行赋值,因为mytale
中存在key1,所以直接赋值即可,我们通过前后的print
可以验证这一点。如果我们尝试对一个mytable
中没有的key进行赋值,那会怎么样呢
首先会查询该普通表的元表中是否有__newindex这个key
- 若没有,则直接在普通表中增加这个key,对其进行赋值操作
- 若存在__newindex,且其指向的是一个table,那么就会在__newindex指向的table中增加这个key,并且进行赋值
有一个非常容易引起迷惑的点我们需要注意的是
__index与__newindex的操作是完全独立的!
所以一开始可能会有些疑惑,为什么上面代码输出会是nil,我们不是明明已经设置了key2的值了吗?我们确实是成功进行了赋值操作,但是根据上面的逻辑,因为mytable
中不存在key2,所以我们实际上是对__newindex所指向的mymetatable
进行了赋值操作。实际上普通表是没有任何变化的,所以我们最后一行代码,尝试索引mytable
的key2,按照3.1.1中的逻辑,因为没有设置__index
方法,所以返回的会是nil。如果我们希望访问到key2,下面的代码就可以做到了
print(mymetatable.key2) --输出 modified2
3.2.2指向function
--指向function
mytable = setmetatable({key1="value1"}, { __newindex = function()
print("该表禁止增加新的key!")
end})
mytable.key2 = "value2" --输出 "该表禁止增加新的key!"
如果__newindex
指向的是function,那么在对一个普通表中不存在的key进行赋值时,会执行这个function。
3.2.3function带有参数的情况
在3.1.2以及3.2.2中的示例只是最基础不带参数的情况,其实在__index
和__newindex
这两个元方法指向function类型时,是会传递参数进来的,直接上代码。
__index
默认传递的两个参数是调用该方法的table
,以及尝试访问的key
mytable = setmetatable({}, { __index = function(t,k)
print("你正在尝试索引mytable中没有的key")
print(t,k)--输出 table: 0x5621b1f07270 key1
end}
)
returnValue = mytable.key1 --输出 "你正在尝试索引mytable中没有的key"
print(returnValue) --输出 nil
而__newindex
默认传递进来的是三个参数,调用该方法的table
,需要赋值的key
以及想要赋予的value
mytable = setmetatable({key1="value1"}, { __newindex = function(t,k,v)
print("该表禁止增加新的key!")
print(t,k,v)--输出table: 0x55a8f8aefbb0 key2 value2
end})
mytable.key2 = "value2" --输出 "该表禁止增加新的key!"
其他的元方法
除了__index
,__newindex
这两个元方法外,还有很多别十分有用的元方法,等我后面用到的时候会陆续添加进来!
最后
理解了元表以及元方法后,在下一章中,我们就可以开始在Lua中真正的去模拟面向对象思想中,封装,多态,继承的特性了!