7.[重要] lua面向对象*
lua的面向对象、类等概念其实都是基于table和metatable实现的
完整代码**
--面向对象实现
--万物之父 所有对象的基类 Object
--封装
Object = {}
--实例化方法
function Object:new()
--一定要使用local,否则就变成全局变量了
local obj = {}
--给空对象设置元表 以及 __index
self.__index = self
setmetatable(obj, self)
return obj
end
--继承
function Object:subClass(className)
--根据名字生成一张表 就是一个类
_G[className] = {}
local obj = _G[className]
--设置自己的“父类”,以实现多态
obj.base = self
--给子类设置元表 以及 __index
self.__index = self
setmetatable(obj, self)
end
--申明一个新的类
Object:subClass("GameObject")
--成员变量
GameObject.posX = 0
GameObject.posY = 0
--成员方法
function GameObject:Move()
self.posX = self.posX + 1
self.posY = self.posY + 1
end
--实例化对象使用
local obj = GameObject:new()
print(obj.posX)
obj:Move()
print(obj.posX)
local obj2 = GameObject:new()
print(obj2.posX)
obj2:Move()
print(obj2.posX)
--申明一个新的类 Player 继承 GameObject
GameObject:subClass("Player")
--多态 重写了 GameObject的Move方法
function Player:Move()
--base调用父类方法 用. 自己传第一个参数
self.base.Move(self)
end
print("****")
--实例化Player对象
local p1 = Player:new()
--使用的Gameobject的posX
print(p1.posX)
p1:Move()
--赋值之后调用的是p1自己的posX
print(p1.posX)
local p2 = Player:new()
print(p2.posX)
p2:Move()
print(p2.posX)输出:
0
1
0
1
****
0
1
0
1封装
- 当使用
: 调用方法时,self代表调用者(默认把调用者传入) - 本质上声明一个“对象”,就是声明一个空表
print("**********封装************")
--面向对象 类 其实都是基于 table来实现
--元表相关的知识点
Object = {}
Object.id = 1
function Object:Test()
print(self.id)
end
--冒号 是会自动将调用这个函数的对象 作为第一个参数传入的写法
function Object:new()
--self 代表的是 我们默认传入的第一个参数
--对象就是变量 返回一个新的变量
--返回出去的内容 本质上就是表对象
local obj = {}
--元表知识 __index 当找自己的变量 找不到时 就会去找元表当中__index指向的内容
self.__index = self
setmetatable(obj, self)
return obj
end注意!!!!
local myObj = Object:new()
print(myObj)
-- 因为找不到myObj的id,但由于设置__idex,所以会输出Object的id 1
print(myObj.id)
myObj:Test()
-- 对空表中 申明一个新的属性 叫做id(没有设置__newIndex,会设置到本身)
myObj.id = 2
-- 默认吧myObj传入,所以输出2
myObj:Test()输出:
table: 0059A5A0
1
1
2继承
--面向对象 类 其实都是基于 table来实现
--元表相关的知识点
Object = {}
Object.id = 1
function Object:Test()
print(self.id)
end
--冒号 是会自动将调用这个函数的对象 作为第一个参数传入的写法
function Object:new()
--self 代表的是 我们默认传入的第一个参数
--对象就是变量 返回一个新的变量
--返回出去的内容 本质上就是表对象
local obj = {}
--元表知识 __index 当找自己的变量 找不到时 就会去找元表当中__index指向的内容
self.__index = self
setmetatable(obj, self)
return obj
end
print("**********继承************")
--C# class 类名 : 继承类
--写一个用于继承的方法
function Object:subClass(className)
-- _G知识点 是总表 所有声明的全局标量 都以键值对的形式存在其中
_G[className] = {}
--写相关继承的规则
--用到元表
local obj = _G[className]
self.__index = self
--子类 定义个base属性 base属性代表父类
obj.base = self
setmetatable(obj, self)
end
--print(_G)
--_G["a"] = 1
--_G.b = "123"
--print(a)
--print(b)
Object:subClass("Person")
local p1 = Person:new()
print(p1.id)
p1.id = 100
print(p1.id)
p1:Test()
Object:subClass("Monster")
local m1 = Monster:new()
print(m1.id)
m1.id = 200
print(m1.id)
m1:Test()输出:
1
100
100
1
200
200注意!!!*
Object:subClass("Person")
local p1 = Person:new()
--输出1 Object的id
print(p1.id)输出的是Object的id
因为:
- p1调用Person:new(),会将person设置为自己的元表,并设置__index指向Person
- 而Person又调用Object:SubClass(),将Object设置为自己的元表 并设置__index指向Object
即 p1 -> Person -> Object
再根据__index会持续向上寻找最后输出1
多态
Object = {}
Object.id = 1
function Object:Test()
print(self.id)
end
--冒号 是会自动将调用这个函数的对象 作为第一个参数传入的写法
function Object:new()
--self 代表的是 我们默认传入的第一个参数
--对象就是变量 返回一个新的变量
--返回出去的内容 本质上就是表对象
local obj = {}
--元表知识 __index 当找自己的变量 找不到时 就会去找元表当中__index指向的内容
self.__index = self
setmetatable(obj, self)
return obj
end
--C# class 类名 : 继承类
--写一个用于继承的方法
function Object:subClass(className)
-- _G知识点 是总表 所有声明的全局标量 都以键值对的形式存在其中
_G[className] = {}
--写相关继承的规则
--用到元表
local obj = _G[className]
self.__index = self
--子类 定义个base属性 base属性代表父类
obj.base = self
setmetatable(obj, self)
end
print("**********多态************")
--相同行为 不同表象 就是多态
--相同方法 不同执行逻辑 就是多态
Object:subClass("GameObject")
GameObject.posX = 0;
GameObject.posY = 0;
function GameObject:Move()
self.posX = self.posX + 1
self.posY = self.posY + 1
print(self.posX)
print(self.posY)
end
GameObject:subClass("Player")
function Player:Move()
--base 指的是 GameObject 表(类)
--这种方式调用 相当于是把基类表 作为第一个参数传入了方法中
--避免把基类表 传入到方法中 这样相当于就是公用一张表的属性了
--我们如果要执行父类逻辑 我们不要直接使用冒号调用
--要通过.调用 然后自己传入第一个参数
self.base.Move(self)
end
local p1 = Player:new()
p1:Move()
p1:Move()
--目前这种写法 有坑 不同对象使用的成员变量 居然是相同的成员变量
--不是自己的
local p2 = Player:new()
p2:Move()输出:
1
1
2
2
1
1注意
如何实现子类调用父类的方法
function Object:subClass(className)
-- _G知识点 是总表 所有声明的全局标量 都以键值对的形式存在其中
_G[className] = {}
--写相关继承的规则
--用到元表
local obj = _G[className]
self.__index = self
--子类 定义个base属性 base属性代表父类
obj.base = self
setmetatable(obj, self)
end给继承方法增加一个base,给新的类(表)设置一个base指向self(因为调用是采用Object:subClass,此时的self是Object)
坑!!!!!**
对于
Object:subClass("GameObject")
GameObject.posX = 0;
GameObject.posY = 0;
function GameObject:Move()
self.posX = self.posX + 1
self.posY = self.posY + 1
print(self.posX)
print(self.posY)
end
GameObject:subClass("Player")
function Player:Move()
--base 指的是 GameObject 表(类)
--这种方式调用 相当于是把基类表 作为第一个参数传入了方法中
--避免把基类表 传入到方法中 这样相当于就是公用一张表的属性了
--我们如果要执行父类逻辑 我们不要直接使用冒号调用
--要通过.调用 然后自己传入第一个参数
self.base:Move()
end如果我们使用self.base:Move(self)
此时会将base默认传入base(父类)
此时如果我们
local p1 = Player:new()
p1:Move()
--目前这种写法 有坑 不同对象使用的成员变量 居然是相同的成员变量
--不是自己的
local p2 = Player:new()
p2:Move()我们会发现输出是
1
1
2
2p1和p2共用父类GameObject的posX和posY,这显然是不向对象的
因为我们每次调用base:Move都会默认吧GameObject传入,所以两次操作的都是GameObject这张表的posx和posy
为避免这个问题,当我们想要在子类使用父类方法的时候要使用self.base.Move(self)
GameObject:subClass("Player")
function Player:Move()
--base 指的是 GameObject 表(类)
--这种方式调用 相当于是把基类表 作为第一个参数传入了方法中
--避免把基类表 传入到方法中 这样相当于就是公用一张表的属性了
--我们如果要执行父类逻辑 我们不要直接使用冒号调用
--要通过.调用 然后自己传入第一个参数
self.base.Move(self)
end当我们调用
local p1 = Player:new()
p1:Move()
p1:Move()
--目前这种写法 有坑 不同对象使用的成员变量 居然是相同的成员变量
--不是自己的
local p2 = Player:new()
p2:Move()此时Player:Move的self分别是p1和p2
那么我们操作的posX和posY就分别是p1和p2自己的posX和posY,此时输出
1
1
2
2
1
1为什么会这样呢采用self.base.Move(self)就可以分别使用两个表的posX和posY呢?
因为在Move方法
--伪代码
function GameObject:Move()
self.posX = self.posX + 1
self.posY = self.posY + 1
end
p1:Move()此时我们传入的self其实是表p1,此时p1是没有posX和posY的,调用self.posX的时候实际上使用的是GameObject的posX,得出结果1,此时赋值
相当于p1.posX=1相当于给p1新添加了一个元素posx=1
这篇文章详细介绍了如何在Lua中实现面向对象编程(OOP),主要基于table和metatable机制。文章分为封装和继承两部分:
- 封装:通过元表
__index实现属性查找,使用冒号语法自动传递self参数,展示了如何创建类和实例化对象。 - 继承:利用
_G全局表和元表机制实现子类继承父类功能,包括方法重写(多态)和通过base属性调用父类方法。
完整代码示例演示了从基类Object派生子类GameObject和Player的过程,包括成员变量/方法的定义、实例化及多态调用。输出结果验证了不同实例间的数据独立性。


