加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

Lua 模拟面向对象

发布时间:2020-12-14 21:53:22 所属栏目:大数据 来源:网络整理
导读:分析 Lua中并不存在类的概念,但是我们可以通过使用table和元表元方法来模拟类和类的实例的特性和行为 基础: Lua的table可以存放各个类型的值,也可以存放函数(函数在Lua中是第一类值与其他值一样使用,可以被存放在变量中,也可以存放在表中,可以作为函

分析

Lua中并不存在类的概念,但是我们可以通过使用table和元表元方法来模拟类和类的实例的特性和行为

基础:

Lua的table可以存放各个类型的值,也可以存放函数(函数在Lua中是第一类值与其他值一样使用,可以被存放在变量中,也可以存放在表中,可以作为函数的参数,还可以作为函数的返回值),那么,我们就可以使用table来生成类的原型,存放类的成员值和成员变量。
Lua的元表提供了类似C++ 中继承的特性,如果我将a的元表设置为b,那么a将会拥有b中的值和函数,可以把a看成b的子类。

方案:

用一个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)

改进:
我们需要对一个函数操作时,指定实际的操作对象,这就需要一个额外的参数,就像C++中表示类自己使用this关键字,Lua中使用self

-- 改进上述代码
-- 定义类
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中本来没有类的概念
Lua中表示一个类,是通过创建一个用于其他对象的原型table,原型也是一个常规对象,我们可以直接通过原型调用对应的方法,当其他对象(“类”的实例)遇到一个未知操作时,会去原型中查找对应的方法,原型类似C++ 继承思想中父类的概念。
在Lua中实现原型非常简单,使用setmetatable指定表的原型

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,可以看到函数与父类函数地址不同

参考:
http://www.360doc.com/content/15/1117/01/29224271_513719890.shtml
http://www.jellythink.com/archives/529

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读