前言

Lua是一种轻量级的脚本语言,在unity开发中经常用来做热更新相关的解决方案,我们现在的程序开发往往都是面向对象的,而Lua中是不存在类的概念的,让我们来看一下在Lua中如何实现面向对象编程。

lua中的一些方法:

  • select('#', …) 返回可变参数的长度。
  • select(n, …) 用于返回从起点 n 开始到结束位置的所有参数列表。

一、Lua中的数据类型

Lua中共有8种数据类型,简单数据类型:number、boolean、string、nil,复杂数据类型:function、userdata、thread、table。Lua中没有类的概念,所有面向对象的实现都是基于table来实现的。在table中我们可以定义一些变量,去模拟一个类的封装,比如:

Student = 
{
	age = 18,
	sex = true,

	Up = function()
		Student.age = Student.age + 1
		print("长大了")
	end,

	Learn = function(t)
		print(t.name .. "好好学习")
	end
}

 关于使用表中的成员,也和C#、C++等语言类似,但更像是类的静态成员,通过: 表名.成员变量名 调用,并且,可以在表外添加声明成员。而对于成员方法的调用分为 表名.成员方法() 和 表名:成员方法() 两种,前者是普通的调用,后者是将方法调用者作为第一个参数传入,方法中的self就代表着传入的第一个参数,对于上述表成员的调用,可看示例代码:

print(Student.age)
Student.Up()
print(Student.age)
//在表外声明成员变量
Student.name = "Happy.Q"
Student.Learn(Student)
Student:Learn()
//在表外声明成员方法
function Student:Speak()
	print(self.name .. "说话")
end
Student:Speak()
}

运行结果:

lua变量转换成字符串 lua 数据类型转换_lua

二、_G

_G是一个Lua程序中的总表,也称为"大G表",它实际是一个table,里面存放了整个Lua程序中声明的所有全局变量,而本地变量不会被存放到大G表中。我们可以通过遍历大G表得到我们目前声明的所有全局变量。

代码如下:

for key,value in pairs(_G) do
	print(key,value)
end

三、元表

元表的概念让table之间有了父子关系,任何一个表都可以设置为另一个表的元表,则另一个表就成为这个表的子表。设置元表的方法:

table1 = {}
table2 = {}
//设置元表函数,第一个参数:子表,第二个参数:元表
setmetatable(table1,table2)

元表中有一个很重要的特定操作:__index
首先注意,前面是两个下划线。__index的作用是,当在表中找不到索引时,会在元表的__index指向的表中寻找。通过一个简单的例子来看一下__index的作用:

table1 = {}
table2 = 
{
	age = 18
}

//__index的设置最好在表外
table2.__index = table2

//将table1设为table2的子表
setmetatable(table1,table2)

print(table1.age)

运行结果:

lua变量转换成字符串 lua 数据类型转换_开发语言_02

 显然,table1中是不存在age的定义的,但通过它的元表table2的__index所指向的表,即table2自身,就能找到age。看到这里,熟悉面向对象的读者就会发现,这一特性类似于继承关系。关于元表,还有很多特定操作,比如__tostring,__tocall等,这里不做过多介绍,读者可以看Lua的官方文档进行学习,Lua的学习成本还是较低的,相信大家很容易就能学会。

四、面向对象

前面做了很多的铺垫,终于迎来了本文的重头戏——Lua中实现面向对象。面向对象的三大特性:封装、继承、多态。读完前面的内容,我们大概总结出来,表的定义可以实现封装,元表的概念和特性可以实现继承,其实多态也蕴含在表的方法中,下面让我们边实现面向对象,边去发现多态的存在。 

1.封装

首先,我们实现一个类似C#中的万物之父——Object的存在

代码如下:

//万物之父 所有对象的基类
Object = {}
//为Object创建一个实例化对象的方法
function Object:new()
	local obj = {}
	//self就是第一个参数,即Object
	//将__index指向Object。并将新定义的表设为Object的元表
	//则新定义的表就有了Object中的属性
	self.__index = self
	setmetatable(obj,self)
	return obj
end

这样,我们就完成了一个基本的万物之父的创建,我们便可以用Object的new方法实例化一个Object的对象:

local obj = Object:new()

2.继承

我们现在有了一个万物之父,那如何做到继承这个Object呢?在C#、C++中,是通过 类名:继承的类 来实现继承的,在Java中则是通过 类名 extends 继承的类 来实现的继承。我们在Lua中似乎不能通过这种符号或关键字的形式将两个表联系起来,所以我们还是考虑为Object来写一个方法实现继承。

代码如下:

function Object:subClass(className)
	//为大G表添加一个名为className的表,就是创建了一个全局表
	//表示新创建的类
	_G[className] = {}
	//将Object的__index指向自身
	self.__index = self
	//将Object作为新创建的表的元表
	setmetatable(_G[className],self)
end

这样一来就实现了一个继承关系,结合上述封装,我们可以定义一个Person类,并实例化一个person对象:

//创建一个Person类,继承自万物之父Object
Object:subClass("Person")
//为Person类添加一个成员变量
Person.id = 10
//实例化一个person对象
person = Person:new()
print(person.id)

运行结果:

lua变量转换成字符串 lua 数据类型转换_开发语言_03

 3.多态

前面已经实现了封装和继承,下面我们来看一下如何实现多态。我们先来想一下,其它面向对象的高级语言是如何实现的多态,这里我们只考虑子类对父类中虚方法的重写,以C#为例,我们是先在父类中用virtual修饰方法,再在子类中用override修饰重写同名方法,如果想基于父类方法重写,则需要用base关键字。在Lua中是不存在这些关键字的,那该怎么办呢?其实很简单,我们可以构造一个base,改一下Object中的继承方法:

function Object:subClass(className)
	//为大G表添加一个名为className的表,就是创建了一个全局表
	//表示新创建的类
	_G[className] = {}
	//将Object的__index指向自身
	self.__index = self
	//构建一个base,指向父类
	_G[className].base = self
	//将Object作为新创建的表的元表
	setmetatable(_G[className],self)
end

这样,base就代表着父类,所以在子类重写方法时,还想继承父类的方法,就可以这样做:

function Person:Speak()
	print(self.name.."说话")
end

Person:subClass("Student")

function Student:Speak()
	//这一步很重要,不能写成:调用,否则传的self是self.base
	//一定要这样写!!
	self.base.Speak(self)
end

这里格外强调一下注释中的内容!这是个大坑,如果这里用了:调用方法,那么将把父类传到了方法中,出来的结果将是父类的name说话。这样,我们就完成了保留父类方法并进行重写,完成了多态的实现。

参考链接:

(105条消息) Lua语言实现面向对象