Lua 面向对象(详解)
参考文章:
https://blog.csdn.net/linxinfa/article/details/103254828
https://zhuanlan.zhihu.com/p/115159195
https://blog.codingnow.com/cloud/LuaOO
https://blog.codingnow.com/2006/06/oo_lua.html
Lua的面向对象主要是参考云风大神的进行编写,在云风大神源码的基础上考虑了一点细节,构造代码如下:
local _class = {}
function class(super)
local tbClassType = {}
tbClassType.Ctor = false
tbClassType.super = super
tbClassType.New = function(...)
local tbObj = {}
do
local funcCreate
funcCreate = function(tbClass,...)
if tbClass.super then
funcCreate(tbClass.super,...)
end
if tbClass.Ctor then
tbClass.Ctor(tbObj,...)
end
end
funcCreate(tbClassType,...)
end
-- 防止调用Ctor初始化时,在Ctor内部设置了元表的情况发生
if getmetatable(tbObj) then
getmetatable(tbObj).__index = _class[tbClassType]
else
setmetatable(tbObj, { __index = _class[tbClassType] })
end
return tbObj
end
local vtbl = {}
_class[tbClassType] = vtbl
setmetatable(tbClassType, { __newindex =
function(tb,k,v)
vtbl[k] = v
end
})
if super then
setmetatable(vtbl, { __index =
function(tb,k)
local varRet = _class[super][k]
vtbl[k] = varRet
return varRet
end
})
end
return tbClassType
end
baseType = class()
function baseType:Ctor(x)
print("baseType Ctor")
self.x = x
-- do something
-- 包括setmetatable(self,{xxx})
-- 由于在New方法内部已经考虑如果在Ctor函数中设置元表,外部则直接对__index赋值。毕竟一个表只能关联一张元表
end
function baseType:PrintX()
print(self.x)
end
function baseType:Hello()
print('hello baseType')
end
test = class(baseType)
function test:Ctor() -- 定义 test 的构造函数
print("test Ctor")
end
function test:Hello() -- 重载 base_type:hello 为 test:hello
print("hello test")
end
a = test.New(1)
a:PrintX()
a:Hello()
a1 = test.New(2)
a1:PrintX()
a1:Hello()
a:PrintX()
输出结果:
下面对上述代码进行剖析以及注释。上述代码不仅仅实现类的封装以及继承,对元表中的元方法进行重载也已经考虑。
-- 记录所有的类模板以及其对应的方法,同时可以利用在继承关系下快速索引父级属性以及方法。
local _class = {}
-- 构建一个类,返回一个类对象,参数为基类(可不填)
function class(super)
-- 可以理解为构建一个类模板
local tbClassType = {}
-- 构造函数,可以理解为C++的纯虚函数(接口)
tbClassType.Ctor = false
-- 子类记录父类
tbClassType.super = super
-- New成员方法(匿名方法,闭包),只要用于通过类模板实例化一个类对象(实例)
tbClassType.New = function(...)
-- 实例类
local tbObj = {}
do
-- 用于递归调用,如果有基类,且基类有构造函数,那么先调用基类的构造函数;否则直接调用当前类的构造函数
local funcCreate
funcCreate = function(tbClass,...)
if tbClass.super then
-- 如果有基类,优先调用基类的ctor函数(递归,直至没有上一级为止)
funcCreate(tbClass.super,...)
end
if tbClass.Ctor then
-- 调用构造函数
tbClass.Ctor(tbObj,...)
end
end
funcCreate(tbClassType,...)
end
-- 防止调用Ctor初始化时,在Ctor内部设置了元表的情况发生
-- _class[tbClassType]就是类模板绑定的成员,类实例同样需要持有
if getmetatable(tbObj) then
getmetatable(tbObj).__index = _class[tbClassType]
else
setmetatable(tbObj, { __index = _class[tbClassType] })
end
return tbObj
end
-- 这个table保存所有类模板的成员,实现过程中主要保存的是类的成员方法
local vtbl = {}
_class[tbClassType] = vtbl
-- tbClassType 新增成员的时候,存到vtbl中
setmetatable(tbClassType, { __newindex =
function(tb,k,v)
vtbl[k] = v
end
})
if super then
-- 如果当前类继承了父类,那么在访问成员时,如果当前类中不具有该成员,则优先从_class中保存的父级中读取;
-- 然后再将父类的成员赋值给当前类模板的vtbl容器。
-- 简单地说,就似乎如果一个类实例有需要访问的成员则直接调用,否则从该类的上一级查找。
setmetatable(vtbl, { __index =
function(tb,k)
local varRet = _class[super][k]
vtbl[k] = varRet
return varRet
end
})
end
return tbClassType
end
baseType = class()
function baseType:Ctor(x)
print("baseType Ctor")
self.x = x
end
function baseType:PrintX()
print(self.x)
end
function baseType:Hello()
print('hello baseType')
end
test = class(baseType)
function test:Ctor() -- 定义 test 的构造函数
print("test Ctor")
end
function test:Hello() -- 重载 base_type:hello 为 test:hello
print("hello test")
end
a = test.New(1)
a:PrintX()
a:Hello()
a1 = test.New(2)
a1:PrintX()
a1:Hello()
a:PrintX()
说明点
上面有些细节的地方需要阅读一下知识点,有助于理解上文代码。
__index和__newindex
当我们一个表绑定一个元表时,如果为访问表的属性,那么触发的是__index,而如果是修改表的属性,那么触发的是__newindex。也就是说,我们在修改表的属性时并不会遵循__index的查找规则!
tbTest = {}
setmetatable(tbTest,{ __index = function(mytable, key)
if key == "key2" then
return "metatablevalue"
else
print(key)
end
end})
tbTest.name = "ufgnix0802" -- 这里并不会触发__index这个元方法
print(tbTest.name) -- 触发__index元方法!
getmetatable(tbTest).__newindex = function(mytable, key, value)
rawset(mytable, key, "\""..value.."\"")
end
tbTest.key = "ufgnix0802" -- 触发__newindex元方法!
‘self’和’:'的关系
在lua中,表拥有一个标识:self。self类似于this指针,大多数面向对象语言都隐藏了这个机制,在编码时不需要显示的声明这个参数,就可以在方法内使用this(例如C++和C#)。在lua中,提供了冒号操作符来隐藏这个参数,例如:
local t = {a = 1, b = 2}
function t:Add()
return (self.a + self.b)
end
print(t:Add())
冒号的作用有两个:1. 对于方法定义来说,会增加一个额外的隐藏形参(self);2. 对于方法调用来说,会增加一个额外的实参(表自身)。
冒号只是一种语法机制,提供的是便利性,并没有引入任何新的东西。使用冒号完成的事情,都可以使用点语法来完成。看下面的例子:
local t = {a = 1, b = 2}
function t:Add()
return (self.a + self.b)
end
function t.Sub(self)
return (self.a - self.b)
end
print(t.Add(t))
print(t:Sub())
从上述我们可以很清楚的知道,‘self’和‘:’配对出现,而我们也可不使用这种方式,但是必须传递类本身。那么关键问题来了,如果方法要调用的方法使用了’:‘符号,但我们又不想使用’:'调用呢?那么是不是我们得传递一个类对象本身?这也是上文代码New函数中funcCreate的调用方式为什么多了一个参数,也就是第一个参数tbClass的原因了。
同时,我们需要留意一个细节,那就是上文代码第30行,我们传入的是tbObj,即我们即将构建的类对象实例的指针,而不是第90行的test对象本身。这也是为什么在第92行打印self 的地址跟test不相等的原因。感兴趣的可以尝试一下,self的地址跟101行的a以及105行的a1相同。
实际使用实例
文章链接索引:
https://blog.csdn.net/qq135595696/article/details/128827673