Lua 模拟面向对象
分析Lua中并不存在类的概念,但是我们可以通过使用table和元表元方法来模拟类和类的实例的特性和行为 基础:Lua的table可以存放各个类型的值,也可以存放函数(函数在Lua中是第一类值与其他值一样使用,可以被存放在变量中,也可以存放在表中,可以作为函数的参数,还可以作为函数的返回值),那么,我们就可以使用table来生成类的原型,存放类的成员值和成员变量。 方案:用一个table实现类的原型,可以把这个table看成一个类,另一个表使用metatable指定原型table为其元表,那么它就能使用原型的所有方法。 关键:模拟面向对象可以分解为类的定义和类的实例化两个问题,类的定义主要是实现类的成员值和成员函数的定义,以及如何让子类拥有父类的方法集。类的实例化主要解决实例要如何共享方法集,但独享自己的成员值。 开始模拟如何定义类成员值和类成员函数我们将table堪称一个类,table中的值(非函数)看作成员值,table中的函数值看作成员函数 -- 定义类
Account = {}
-- 定义类成员
Account.balance = 0
-- 定义类成员函数
function Account.withDraw(v)
Account.balance = Account.balance - v
end
Account.withDraw(10)
print(Account.balance) --输出-10
这样做可以使用成员值和成员函数,但是在面向对象的思想中,类的各个实例之间应该互相不影响,每个对象的实例都是一个单独的对象。 -- 定义类
Account = {}
-- 定义类成员
Account.balance = 0
-- 定义类成员函数
function Account.withDraw(v)
Account.balance = Account.balance - v
end
Account.withDraw(10)
print(Account.balance) --输出-10
a = Account
-- Account = nil --一旦给Account赋值为nil会影响到a,因为a是Accout的引用,这不符合面向对象的思想,面向对象中,不同的对象应该互相不影响
a.withDraw(100)
print(a.balance)
改进: -- 改进上述代码
-- 定义类
Account = {}
-- 定义类的成员值
Account.balance = 0
-- 定义类的带self参数的成员函数
function Account.withDraw(self,v)
self.balance = self.balance - v
end
a = Account -- 这里的a和Account现在是一个值
print(Account) --table: 00DF2508
print(a) --table: 00DF2508
print(Account.withDraw) --function: 00DF10D8
Account = nil --这里给Account指向空
if(a == Account) then --由于a仍指向原来的实例,而Account已经指向空,所以这两个变量不相等
print("a == Account")
else
print("a != Account")
end
print(Account) --nil
print(a) --table: 00DF2508
print(a.withDraw) --function: 00DF10D8
a.withDraw(a,100) --withDraw函数有self参数,这个参数,对应的时a,因为此时执行的时a指向的实例的withDraw方法,不会有上次的问题
print("a.balance:" .. a.balance)
Lua中允许使用冒号来简化书写,冒号就是用于隐藏self参数的一个语法糖,a:withDraw(100)就相当于a.withDraw(a,100) Account = {balance = 0}
function Account:withDraw(v) --函数定义时使用语法糖:
self.balance = self.balance - v
end
a = Account
Account = nil --这里给Account指向空
a:withDraw(100) --函数调用时使用语法糖:
print(a.balance)
类的实例化实例化的方法非常简单,为table构建一个类似C++ 类中的构造函数一样的方法,每次调用这个方法都创建一个新的table并把它的元表设为原型 -- 创建一个Account原型,生成两个"类"的实例a和b
local Account = {} -- 原型
Account.num = 10
function Account:new(o)
o = o or {} --如果参数没有提供table,则创建一个table
setmetatable(o,self)
self.__index = self
return o
end
-- a b是新的table,它们的元表为Account
local a = Account:new({value = 100}) --由于a setmetable元表为self指向的Account,相当于a是Account原型的一个实例,也可以理解为Account是a的父类,a集成了它所有的变量和方法
local b = Account:new({value = 120})
print(a)
print(b)
-- 打印出来的a b是两个不同的table地址,是两个"类"的实例
类的继承在C++ 中,每个对象都是某个特定类的实例,我们必须使用new方法实例化对象,实例化的对象C++ 才会给分配空间,我们才能在对象的数据上做操作,而在Lua中,不需要程序员自己分配空间,即使不实例化一个“类”,仍旧可以使用“类”名调用它的方法,Lua模拟的类和C++ 的类是有区别的,Lua中本来没有类的概念 local Account = {}
Account.num = 10
function Account:new(o)
o = o or {}
setmetatable(o,self) --指定新的实例的元表为Account原型类
self.__index = self
return o
end
function Account:display() --为原型类增加display方法
print(self.num) --self是隐藏的参数,Account:display()相当于Account.display(self)
end
local aa = Account:new({num=22}) --实例aa
aa:display() --相当于aa.display(aa),aa继承了Account的display方法
-- 输出22
print(aa.display) --function: 00D0B4A0
print(Account.display) --function: 00D0B4A0
-- aa.display()会出错,因为aa调用的是Account的display方法,有一个用冒号隐藏的self参数
aa.display(aa) --这样写也可以
-- 输出22
-- 有人说,调用Lua类的属性使用点号,调用其方法使用冒号,其实指的就是这个意思
-- 子类还可以重载父类方法
function aa:display()
print("override father method display!")
end
aa:display() -- 运行时输出override father method display!
print(aa.display) --function: 001E7EB0,可以看到函数与父类函数地址不同
参考: (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |