简介
1. lua面向对象编程是基于元表metatable,元方法__index来实现的,具体元表和元方法的介绍
请见Lua的元表metatable及元方法:
https://blog.csdn.net/sindyra/article/details/103488487
2. 语法糖
语法糖是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用
举例说明:数组char test[100],如果要访问第11个元素,可以这样写:char c = *(test+10),但是用语法糖的话就简单了,直接char c = test[10],看到没?语法糖就是一些简便写法
lua中的语法糖
我们有两种函数定义和调用的方法。一种是用属性的方式,另外一种是通过冒号的形式(其实也是属性)。只不过用冒号形式申明的函数默认会有一个参数self。self指向本身(表)
lua中的函数默认都是有self传递进去的,self相当于C++类中函数的this指针,语法糖会自动给我们传递 self
举例说明:
local a =
{
x = 99
}
-- 打印函数,注意这里要访问表a中的变量x,必须指明self.x或者a.x,不然会报错
function a:Print()
print("function a:test() " ..self.x)
end
-- 想调用a的Print()函数,我们可以这样写,注意参数是a,否则调用出错
a.Print(a)
-- 也可以这样写,即用:代替. 且不用传入参数a
a:Print()
明显第二种方法更简便
2. lua面向对象的原理(基于元表metatable和元方法__index)
如果访问了lua表中不存在的元素时,就会触发lua的一套查找机制,也是凭借这个机制,才能够实现面向对象的
举例说明:
test =
{
}
-- 访问表test中不存在的变量a
print(test.a)
打印结果:nil
原因很简单:表test中不存在变量a,所以打印为nil,但是如果表test有元表metatable的话,情况就不一样了打印结果:nil
元表像是一个备用查找表,假设表A的元表是B,那么在A中找不到的东西就会尝试在B中去找,设置元表的函数如下
setmetatable(A, B),这样表B就被设置为A的元表,当A中查找不到某个变量时就会到B中进行查找
举例说明:
-- 表A
A =
{
}
-- 表B
B =
{
a = 99
}
-- 设置表B为表A的元表
setmetatable(A,B)
-- 再访问表A中不存在的变量a
print(A.a)
打印结果依然为:nil
why?因为表B的元方法__index没有赋值。按照笔者的理解,元方法__index是用来确定一个表在被作为元表时的查找方法打印结果依然为:nil
我们做如下更改,即对表B的元方法进行赋值
代码如下:
-- 表A
A =
{
}
-- 表B
B =
{
a = 99
}
-- 给表B的元方法__index进行赋值
B.__index = B
-- 设置表B为表A的元表
setmetatable(A,B)
-- 再访问表A中不存在的变量a
print(A.a)
打印结果:99
查找过程:访问A.a时,表A中没有a这个变量,但是lua发现表A有元表,即表B,于是再到表B中进行查找,但是lua并不是直接在表B中查找变量a,而是调用表B的元方法__index,如果__index为nil,那就会返回nil。如果__index被赋值为一个表(上面的例子就是__index被赋值为表B自己),那么就会到__index指向的那个表(即表B)中进行查询,于是找到了变量a;如果__index被赋值为一个函数,那么查找时就会返回该函数的返回值打印结果:99。
总结元表的查找步骤:
步骤1.在表中查找,如果找到,返回该元素,找不到则继续步骤2
步骤2.判断该表是否有元表,如果没有元表,返回nil,有元表则继续步骤3
步骤3.判断元表有没有__index方法,如果__index方法为nil,则返回nil;如果__index方法是一个表,则重复步骤1、2、3;如果__index方法是一个函数,则调用该函数,并返回该函数的返回值
3. 面向对象的封装
-- 类Class的声明,其实就是个table,这里有两个成员变量x,y
Class =
{
x = 1,
y = 2
}
-- 设置metatable的元方法__index,指向表Class自己
Class.__index = Class
-- 构造函数,叫什么名字无所谓,这里采用了C++的new名字
function Class:new(x, y)
print("Class:模拟构造函数new()")
-- 新建一个对象,这样通过Class:new()函数创建的每一个实例都是独立的
local tempObj = {}
tempObj.x = x
tempObj.y = y
-- 设置新对象的metatable,谨记:这一步非常重要
setmetatable(tempObj,Class)
-- 返回这个新创建的对象
return tempObj
end
-- 类的其他成员函数1
function Class:Print()
print("Class:Print()")
print("x = "..self.x..", y = "..self.y)
end
-- 类的其他成员函数2
function Class:Add(val)
print("Class:Add()")
self.x = self.x + val
self.y = self.y + val
end
-- 类的其他成员函数3
function Class:Modify()
print("Class:Modify()")
self.x = 11
self.y = 22
end
-- 下面是测试代码
-- 新构造一个类实例
local Obj = Class:new(11,22)
-- 调用函数Print()进行打印
Obj:Print()
-- 调用函数Add()进行加操作
Obj:Add(5)
-- 再次调用函数Print()进行打印,会发现调用Add()函数确实成功了
Obj:Print()
-- 做修改
Obj:Modify()
-- 再次调用函数Print()进行打印,会发现调用Modify()函数确实成功了
Obj:Print()
-- 这里打印出Class本身的数据,会发现数据没有改动,说明是新建的类实例互不影响
print("Class Class.x = "..Class.x..", Class.y = "..Class.y)
测试结果如下:
4.面向对象的继承和多态
-------------------------------------- 基类Class ------------------------------------------
-- 类Class的声明,其实就是个table,这里有两个成员变量x,y
Class =
{
x = 0,
y = 0
}
-- 设置metatable的元方法__index,指向表Class自己
Class.__index = Class
-- 构造函数,叫什么名字无所谓,这里采用了C++的new名字
function Class:new(x, y)
print("Class:模拟构造函数")
-- 新建一个对象,这样通过Class:new()函数创建的每一个实例都是独立的
local tempObj = {}
tempObj.x = x
tempObj.y = y
-- 设置新对象的metatable,谨记:这一步非常重要
setmetatable(tempObj,Class)
-- 返回这个新创建的对象
return tempObj
end
-- 类的其他成员函数1
function Class:Print()
print("Class:Print() x = "..self.x..", y = "..self.y)
end
-- 类的其他成员函数2
function Class:Add(val)
print("Class:Add()")
self.x = self.x + val
self.y = self.y + val
end
-- 类的其他成员函数3
function Class:Modify()
print("Class:Modify()")
self.x = 111
self.y = 222
end
-------------------------------------- 子类SubClass ---------------------------------------
-- 子类SubClass的声明,这里又声明了一个新的变量z
SubClass =
{
z = 0
}
-- 设置元表为Class
setmetatable(SubClass, Class)
-- 设置metatable的元方法__index,指向表SubClass自己
SubClass.__index = SubClass
-- 构造函数
function SubClass:new(x,y,z)
print("模拟构造函数:SubClass")
-- 先调用父类的构造函数,构造出一个父类的实例
local tempObj = Class:new(x,y)
-- 将该对象的元表指向SubClass,谨记:这步非常重要,一定不要弄错了,是SubClass
setmetatable(tempObj,SubClass)
-- 新属性z赋值,有了子类自己的数据,这样就是子类实例了
tempObj.z = z
return tempObj
end
-- 定义一个新的成员函数
function SubClass:SubPrint()
print("SubClass:SubPrint() x = "..self.x..", y = "..self.y..", z = "..self.z)
end
-- 重定义父类的函数Add(),注意:和父类的不同,这里是增加了2倍的val
function SubClass:Add(val)
print("SubClass:Add()")
self.x = self.x + 2*val
self.y = self.y + 2*val
end
------------------------------------- 下面是测试代码 -----------------------------------
-- 构造一个基类实例
local Obj = Class:new(11,22)
-- 调用函数Print()进行打印
Obj:Print()
-- 调用函数Add()进行加操作
Obj:Add(5)
-- 再次调用函数Print()进行打印,会发现调用Add()函数确实成功了
Obj:Print()
-- 做修改
Obj:Modify()
-- 再次调用函数Print()进行打印,会发现调用Modify()函数确实成功了
Obj:Print()
-- 这里打印出Class本身的数据,会发现数据没有改动,说明是新建的类实例互不影响
print("Class Class.x = "..Class.x..", Class.y = "..Class.y)
print("\n")
-- 构造一个子类实例
local SubObj = SubClass:new(1,2,3)
-- 访问父类的函数
SubObj:Print()
-- 访问子类自己的函数
SubObj:SubPrint()
-- 调用Add(),这里会发现实际调用的是子类的Add()函数,即实现了多态
SubObj:Add(5)
-- 再次调用自己的函数,会发现调用自己的Add()函数确实成功了
SubObj:SubPrint()
测试结果如下: