注:本文代码中的->符号均为运行后的输出结果,不算做代码

Contents

前言

说道面向对象,基本就是围绕着表来展开的。lua中的表是一个万能的类型,可以当做数组,哈希表,结构体,等数据类型,主要是搭配元表来实现面向对象。前面会有些lua表的基础简单介绍,如有了解可直接翻找目录 “Lua类型与实例”,因为博主常使用C#进行开发,所以本文章中会有很多关键字与命名风格偏向C#。

Lua中的表:table

表和其他基础类型不同,它是引用类型的,或者叫指针类型。当给变量赋的值为表时,只是把表的引用复制了过去。

表的声明方式:

{{EJS0}}

没错,表仅仅使用{}就可以动态的创建了一个表对象,不需要任何的构造函数或new等关键字来创建。

表可以表示的数据类型

当做数组使用

{{EJS1}}

当做字典使用

{{EJS2}}

当做枚举使用

{{EJS3}}

两种形式的赋值

{{EJS4}}

以上是field作为表元素被赋值,两种写法结果相同,只是表现方式不同,但是使用场景不同,从语意来讲前者可以是个枚举值,或者是个类中的字段,而后者是对字典的操作。

Lua中的函数:function

lua中的函数是第一类值,也就是被当做一种普通的类型可以储存在变量和表中,函数也是引用类型

{{EJS5}}

以上两种方法声明方式都是正确的,只是第一种写法看起来更像是匿名函数,第二种则是普通的静态方法。

储存方法的变量同样可以成为表中一个普通的字段,并且提供了语法糖,让它看起来更像一个函数。

{{EJS6}}

既然方法只是表中的一个普通元素,而元素的key都是作为字符串存在的,所以可以直接拿方法名来索引,也就是Lua中的“反射”。

{{EJS7}}

元表:metatable

元表,metatable,允许改变表的行为,lua提供了许多原方法,可以实现运算符重载、查询、更新等功能。

运算符重载

__add 对应的运算符 ‘+’.
__sub 对应的运算符 ‘-‘
__mul 对应的运算符 ‘*’.
__div 对应的运算符 ‘/’.
__mod 对应的运算符 ‘%’.
__unm 对应的运算符 ‘-‘.
__concat 对应的运算符 ‘..’.
__eq 对应的运算符 ‘==’.
__lt 对应的运算符 ‘<‘.
__le 对应的运算符 ‘<=’.

普通元方法

__call 把table当做一个方法执行
__tostring 修改输出行为
__newindex 对表更新
__index 对表查询

其中最常用的就是__index,当你给一个表A设置了一个元表B,元表B的__index指向了表C,那么当你访问表A一个不存在的元素时,那么Lua就会寻找元表中的__index,也就是所指向的C,如果存在则返回结果,不存在返回nil

{{EJS8}}

设置元表的函数:setmetatable(table, metatable),返回值table,这个返回值一般是在没提前分配表时需要的,如

{{EJS9}}

获取元表的函数:getmetatable(table),返回值metatable。

静态类

类使用Lua中的模块来实现,字段和方法(函数)直接作为表的元素

{{EJS10}}

调用

{{EJS11}}

类型与实例

类型,是面向对象中对数据抽象的原型,而实例则是原型(prototype)克隆的副本。

类型就像是做好一个模板(原型),然后使用这个原型复制出很多新的实例对象。

{{EJS12}}

一个Human类就完成了,只需要Human.New()就可以创建一个Human原型的副本(类型实例化)。

setmetatable函数内,创建了一个新表,并把本table设置为了新表的查询/索引后,返回这个新创建的表。

也就是说返回的这个表对象中是没有元素的,所以通过查询元表的__index所指向的表Say方法和name字段。

{{EJS13}}

另外__index只用于查询,无论有没有该元素,在赋值时都是对当前表对象赋值和修改,所以并不会通过对象修改原型中的成员。

human.Say(human)这个句子看起来可能有些奇怪,为什么还有传进去个human,也就是调用的对象本身呢,但实际上并不奇怪,因为对象成员方法的本质就是普通静态方法(类成员方法),只是许多面向对象的语言会隐藏掉对象方法的第一个隐藏参数,那就是this指针(self),参考https://www.imxqy.com/code/cpp/cclass.html,这篇文章会更好理解。

Lua提供了可以让我们的代码看起来更像面向对象的语法,提供了”:”这个语法糖,它可以在调用时自动把调用的对象传入第一个参数,而方法名也使用:来声明,默认传入第一个参数为self。

改成如下

{{EJS14}}

调用

{{EJS15}}

 

如果仅用过C#和Java这类纯面向对象语言无法理解这个逻辑的话:

可以记为直接将.调用的就是静态方法,而:调用的就是实例方法(但并不推荐这么记,大多数纯Lua开发下不会有什么问题,但在做胶水语言的时候,如和C#互相调用,可能会发生些问题。比如将Lua的一个回调函数传进了C#里,而纯C#开发者如果不了解委托原理那么可能就会出现Lua回调函数中self对象丢失的问题,因为委托是储存了实例对象的函数指针)