Lua

非常非常详细的Lua面向对象(一)——元表与元方法

2019-12-08  本文已影响0人  巨龙饿了

前言

新的项目终于准备要真正开始使用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中真正的去模拟面向对象思想中,封装,多态,继承的特性了!

上一篇下一篇

猜你喜欢

热点阅读