非常非常详细的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中真正的去模拟面向对象思想中,封装,多态,继承的特性了!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352