天天看點

Lua 中用table表來實作類,繼承,以及面向對象

Lua沒有類這一說,但是可以使用表來接近實作類的功能,實作面向對象

這裡依次用表以類的形式寫

注意:

一個模闆在一個檔案裡,一個子產品可看成是個表,表可以寫成類的形式

本文的類實際是一個table類型的表,這裡是一個類用一個單獨的lua檔案來存放

1. 在外界調用類方法

外界的變量擷取到類後可以直接調用類方法

1.1建立一個檔案ClassA,寫objA類:

這些全局方法在表被擷取後可以直接調用被外界調用

objA = {MyTest2 = } -- MyTest2可以通過表名.擷取
new =  -- 不是objA表裡的字段
MyTest = 
objA.test =  -- 是objA表裡的字段,可以在表外直接定義指派修改
function objA.Play()
    print("objA.play")
end
function objA.Sum(num1,num2)
    return num1+num2
end
function objA.ADD(qwq)
    MyTest = qwq --外界可以用方法指派,但是無法通過表擷取,需要用return傳回
    return MyTest
end 
           

這可以說是一個簡單的子產品,不過沒有傳回值,一個子產品可以看成是一個表,有return就是傳回這個表

但是子產品裡最好不要定義表以外的變量了。

用require()擷取檔案中的那個子產品(也就是開始定義的表A)

require()也可以看出是導入和引用類似C的include() C#的using java的Import (個人了解)

可以到菜鳥網上看看關于子產品:http://www.runoob.com/lua/lua-modules-packages.html

1.2 外界擷取的全局類,可以直接用類名.調用類的方法

建立一個檔案Main,使用ClassA的類objA

Main檔案可以直接用表名點函數來使用,可以說這裡的objA是objA類的一個對象

--這裡因為沒寫return,可以直接用那個ClassA子產品的表名點出來使用
require("classA")
objA.Play()
print(objA.Sum(,))
objA.new = 
print(objA.new)--10看起來可以直接在外部修改,但實際并不是對子產品的new操作,而是在表裡新添一個字段
print(objA.MyTest) --無法得到子產品裡的值Mytest,列印nil
print(objA.ADD()) --但是可以通過傳回值得到
           

總結:這裡測試表示的是在沒有return的子產品情況,外界擷取後可以直接通過.來修改表裡的字段,但是如果是在方法裡則不能修改添加表裡的字段,但是可以在方法裡用return來接收表裡的變量

1.3 局部類用return傳回,并且需要接收

建立一個檔案ClassB ,objB是局部變量

此時可以調用,因為objA是全局變量,可以在外部調用

如果定義local類型的,那麼隻對目前檔案有效,外部無法調用,如果外部需要使用呢?

那麼直接在這個檔案尾部加上 return objA ,然後再定義一個變量來接收

如:

-------------------------ClassB.lua  一個子產品
local objB = {}
function objB.Play()
    print("objB.play")
end

function objB.Sum(num1,num2)
    return num1+num2
end
return objB
------------------------Main.lua
local objB = require("classB")--擷取classB檔案子產品
objB.Play() 
print(objB.Sum(,))
           

2 self關鍵字

self對應c#裡的this,c#中關鍵字可以省略,lua中self不能省略

self使用this作為目前對象

self簡單來說就是當我們需要用函數來修改某個值的時候,為了找到是那個類,就把自己傳進去,

函數用self擷取得到好确定

self. xx 就是目前表的xx ,如果有這個變量,就擷取或者修改 ,如果沒有這個值,則在表裡建立變量

2.1 self
------classC.lua
objC = {name = "Y", age = }

function objC.SetName(self,_name)
    self.name = _name
end
function objC.SetAge(self,_age)
    self.age = _age
end

function objC.GetName(self)
    print(self.name)
end

function objC.GetAge(self)
    return self.age
end

return objC   
----- 全局變量類也可以傳回,不過用注意表名,且最好和self使用
--尤其是在接收變量名相同的時候
------------------- Main.lua
local objC = require("classC")
objC.GetName(objC) -- Y  --這裡擷取的時候要傳入目前的變量objC,進而找到class子產品(表)裡的字段
objC.SetName(objC,"YY")
objC.GetName(objC) -- YY
--這裡建議測試有self和沒有self的差別
           

總結:

檔案内部表裡的函數隻有傳入self,并在内部寫了self.xxx ,外界變量擷取後傳入自己變量,才能找到表裡的字段,進行修改讀取操作

如果不在函數參數寫self,不傳入目前表,函數就無法擷取到自己表裡的值。也就無法在表裡進行修改、添加操作。

3 構造函數new 也就是 : (冒号)

冒号的作用就是:定義函數時,給函數的添加隐藏的第一個參數self;

調用函數時,預設把目前調用者作為第一個參數傳遞進去

如果使用.需要傳遞self,如果使用:不需要傳遞self

3.1 調用構造函數進行指派
----- classD檔案
local ObjD = {name="null",age = }
--構造函數
function ObjD:New(_name,_age)
    self.name = _name
    self.age = _age
end
function ObjD:Play()
    print(self.name)
    print(self.age)
end
return ObjD
-------在Main檔案
local objD = require("classD")
objD:New("Ls",) --修改表classD檔案下的表裡字段
objD:Play() --列印出classD裡的字段 --Ls 1
           
3.2 調用構造函數進行修改添加值
------------------classE檔案
local ObjE = {}
--如果使用.需要傳遞,如果使用:不需要傳遞self
function ObjE:New(_name,_age)
    self.name = _name -- ObjE本身沒有字段->添加字段
    self.age = _age
end

function ObjE:Play()
    print(self.name)
    print(self.age)
end

return ObjE 
-----------------Main檔案
local objE = require("classE")
objE:New("Ls",) --表裡沒有該字段,會自己建立字段
objE:Play() -- Ls 2 
           

4 Lua通過元表實作繼承

lua繼承需要自己寫,這裡就是通過一個父類的 New方法,傳入一個table表作傳參,傳回一個新表,這個新表(子table)在函數裡設定了一個元表(父table),

就可以說 寫了一個子類(table類型變量),繼承了父類(有了個元表)

父類

Hero = {attack = }
function Hero:New(o) -- 建立一個o表,并給o表設定元表
    o = o or {}
    setmetatable (o,self)
    self.__index = self
    return o
end

function Hero:AddAttack(_attack)
    self.attack = self.attack + _attack
end

function Hero:ShowAttack()
    print(self.attack)
end

--子類HeroOne繼承Hero
local HeroOne = Hero:New({HP = }) --繼承 ,并且在子類表裡建立了個字段

--調用測試方法
HeroOne:ShowAttack() --10 --此時子類HeroOne有父類的值

Hero:AddAttack()
Hero:ShowAttack()
HeroOne:ShowAttack()

HeroOne:AddAttack() --在子類的字段修改,父類的字段不變
Hero:ShowAttack()
HeroOne:ShowAttack()
           
4.1子類進行擴充方法

子類擴充方法需在子類生成之後寫即可

這裡建立一個子類HeroOne,并且新增若幹方法

local HeroOne = Hero:New({HP = })
function HeroOne:AddHP(_hp)
    self.HK =  -- 給子類添加新的字段
    self.HP = self.HP + _hp
end

function HeroOne:ShowHP()
    print(self.HP)
end

function HeroOne:ShowHK()
    print(self.HK)
end

--------------測試

HeroOne:AddHP() --  在原來父類的基礎上增加
HeroOne:ShowHP() --300
HeroOne:ShowHK() --2000 
for k , v in pairs(HeroOne) do
    print(k,v)
end
           

最後循環周遊子類:列印子類的方法和字段,沒有父類的構造方法、普通方法。

說明周遊隻會周遊到裡面的類裡的字段,子類繼承也隻繼承父類裡的字段

4.2子類覆寫父類方法

Lua的覆寫方法和C++ C#覆寫方法類似,

如果是子類調用子類的方法,而這個子類的方法名和父類方法名相同,

lua會自動調用子類的覆寫方法

總結

最後總結一下:

因為lua沒有類這一說,是以我們用表來代替類(因為表可以寫成數組,字典,可以寫表方法(表庫)),把表寫成類的形狀

因為類有字段,是以我們就在表上加字段,并且指派。(使用字典的形式)

因為類有方法,是以我們就用fucntion 表名.xxx 來定義表的方法,元表也可以定義元方法

因為類有構造函數,是以我們用冒号:來區分,不過這裡的冒号隻是表示說預設第一個方法參數是self,使用self可以讓表通過調用表方法給表裡 指派、修改、添加字段的作用

因為類可以被繼承,是以我們就用setmetatable()來給2個表設定子類(表)父類(元表)(使用元表)

子類表繼承了父類的字段,但是沒有父類的方法,不過卻可以調用父類的方法。

子類也可以繼續定義擴充方法,甚至可以覆寫父類的方法

繼續閱讀