Lua面向對象基礎

Lua面向對象基礎

來自專欄 Unity3D從入門到放棄13 人贊了文章

一:元表與元方法

二:Lua全局環境

三:Lua模塊

四:Lua面向對象簡單實現

五:Lua面向對象高級


一:元表與元方法

1 元表與與元法方概念

Lua中每個值都可具有元表。元表是普通的Lua表,定義了原始值在某些特定操作下的行為。例如,當table作為加法的操作數時,Lua檢查其元表中的「__add」欄位是否有個函數。如果有,Lua調用它執行加法。我們稱元表中的鍵為事件(event),稱值為元方法(metamethod)。前述例子中的事件是"__add",元方法是執行加法的函數。

Lua創建新的table的同時不會創建元表

t={}

print(getmetatable(t)) -->nil

可以使用setmetatable(t,t1)給t設置元表為t1。在Lua中,只能設置table的元表。若要設置其他類型的元表,則必須通過C代碼來完成。

2. 算術類的元方法

我們在此要實現兩個table的算術運算,在此以加法作為示範

local t1 = {};

local t2 = {};

local result = t1 + t2;

當我們直接將兩個table相加的時候,程序是通不過的。因此,為了滿足我們這種需求, Lua允許我們修改元表。

一個元表,其實就是一個table值,所以,我們只需要新建一個table,添加元方法即可。比如加法運算的元方法就是:__add,這是Lua規定的。只要某個值的元表裡含有__add 這個元方法,那就可以使用+號進行運算。如下圖所示:

其實,Lua已經為我們規定好了各種算術操作符的元方法名字,: __add:加法? __sub:減法? __mul:乘法 __div:除法? __unm:相反數? __mod:取模? __pow:乘冪? 具體的使用方式和add類似,不再詳細敘述。 下面我們再看一個有關__add元方法的示例。代碼如下所示: -- 創建一個元表 local mt = {} mt.__add = function(s1, s2) local result = "" if s1.sex == "boy" and s2.sex == "girl" then result = "完美的家庭。" elseif s1.sex == "girl" and s2.sex == "girl" then result = "不好"; else result = "不好" end return result; end -- 創建兩個table,可以想像成是兩個類的對象 local s1 = {name = "Hello",sex = "boy"} local s2 = {name = "Good",sex = "girl"} -- 給兩個table設置新的元表 setmetatable(s1, mt); setmetatable(s2, mt); -- 進行加法操作 local result = s1 + s2; print(result);

注意事項

1.兩個具有不同元表的值進行算術操作(比如加法)之前舉例的時候,兩個table相加, 這兩個table都是具有相同的元表的,所以沒有任何問題。

那麼,如果兩個table或者兩個進行相加操作的值,具有不同的元表呢? 對於這種情況,Lua是這樣處理:

a.如果第一個值有元表,就以這個元表為準。

b.否則,如果第二個值有元表,就用第二個值的元表。

c.如果兩個值都沒有元表,或者沒有對應的元方法,那麼,就會報錯。

2.關係類的元方法

除了加法減法這些算術類的操作之外,大於小於等這些關係類的操作也是有元方法的:

__eq:等於

__lt:小於

__le:小於等於

如果對兩個具備不同元表的值進行這些比較操作,就會報錯,一定要注意,這和加減法的規則不一樣。

3. __index的使用

設想一下,當我們獲取一個table中不存在的元素的時候。默認的返回nil,但是,如 果我們不希望這樣呢?我們希望在訪問不存在的欄位時,進行一些自定義的操作呢?沒 問題,Lua滿足了我們,那就是,__index元方法。在使用加法操作時,會查找__add元 方法。那麼,在調用table不存在的欄位時,會調用__index元方法,這是一樣的規則。

下面我們看具體的實例。

當調用table中不存在的欄位時,會調用table元表的__index元方法,這個剛剛我們已經說過了。

但是,如果這個__index元方法是一個table的話,那麼,就會在這個table里查找欄位,並調用。如下代碼所示:

上面代碼中當我們調用不存在的欄位時候,就會去元表裡面查找,元表可以是 一個函數也可以是一個table。另外調用函數的時候也會去元表裡面查找,如下代碼所示:

local smartMan = { name = "none", age = 25, money = 9000000, sayHello = function() print("大家好,我是 聰明的豪。"); end } local t1 = {}; local t2 = {} local mt = {__index = smartMan} setmetatable(t1, mt); setmetatable(t2, mt); print(t1.money); t2.sayHello();

4. __newindex的使用

有的時候我們要對table中某個不存在的欄位賦值。沒錯,我們直接就能賦值了, 不會報錯的。但是有的時候我想監控這個操作,如果有人想對table不存在的欄位進 行賦值的時候,我想進行一些額外的處理呢?

這時候就要用到__newindex。 大家要記住這句話:__index用於查詢,__newindex用於更新。如下代碼所示 :

local smartMan = { name = "none", money = 9000000, sayHello = function() print("大家"); end } local t1 = { sayHello = function() print("大家two"); end}; local mt = { __index = smartMan, __newindex = function(table, key, value) print(key .. 「欄位是不存在的不要試圖給它賦值"); end } setmetatable(t1, mt); t1.sayHello = function() print("en"); end; t1.sayHello(); 問題:大家將 local t1 = {sayHello = function() print("大家two"); end}; 修改為t1={},看看結果如何

__newindex元方法被調用的時候會傳入3個參數:table本身、欄位名、想要賦予

值。同__index一樣,__newindex元方法也可以賦予一個table值。

local smartMan={name="none"}local other = {name = "大家好,我是很無辜的table"} local t1={}local mt = { __index = smartMan, __newindex = other} setmetatable(t1, mt); print("other的名字,賦值前:" .. other.name); t1.name = "小偷"; print("other的名字,賦值後:" .. other.name); print("t1的名字:" .. t1.name);

這就是__newindex的規則:

a.如果__newindex是一個函數,則在給table不存在的欄位賦值時,會調用這個函數。

b.如果__newindex是一個table,則在給table不存在的欄位賦值時,會直接給__newindex的table賦值。

5. 忽略元表

有的時候,我們想忽略元表__index和__newindex功能,該如何做呢,如下所示:

local smartMan = { name = "none「}local t1 = { hehe = 123 }local mt = { __index = smartMan, __newindex =function(t, k, v) print("別賦值!");end }setmetatable(t1, mt); print(rawget(t1, "name")); print(rawget(t1, "hehe")); rawset(t1, "name", "小偷"); print(t1.name);

通過rawget函數可以忽略元表的__index功效,純粹地從t1中調用欄位。

rawget的第一個參數是要調用的table,第二個參數是table的欄位名。

因此,通過rawget調用t1的name欄位,只能返回nil,而調用hehe欄位,則能正確取

得值。

同樣的是,rawset函數可以忽略元表的__newindex功效,純粹地給t1賦值。

二:Lua全局環境

Lua將所有的全局變數保存在一個常規的table中,這個table稱之為環境(_G),使 用下面的代碼可以列印當前環境中所有全局變數的名稱

for n in pairs(_G) do print(n) end

1. 全局變數的原型

在Lua中,要聲明全局變數很簡單,那就是定義變數的時候,前面不要加上 local。這個神秘的全局環境,其實本質上也是一個table,它把我們創建的全局變數都保存到一個table里了。而這個table的名字是:_G

-- 定義一個全局變數

gName = "我是全局變數";

-- 用三種方式輸出變數的值

print(gName);

print(_G["gName"]);

print(_G.gName);

2. 使用__index保留原來的_G

我們可以利用__index來解決這個問題,如下代碼:

我們給新的table設置一個元表,這個元表的__index元方法就是_G。 於是,當新的環境里找不到print欄位時,就會去_G里尋找。

三:Lua模塊

1. 一個簡單的模塊

1. 一個簡單的模塊 --我們來看看一個簡單的模塊,新建一個文件,命名為game.lua,代碼如下 game = {}? function game.play() print("開始吧"); end function game.quit() print("結束了"); end return game; 當我們使用的時候,需要用到我們前面講過的require game = require("game"); game.play();

2. 最終解決方案

為了更好的實現這種功能,lua內部已經為我們封裝好了這種功能,如下代碼所示:

module("game", package.seeall);local level=1function setLevel(le) level=leendfunction getLevel() return levelendfunction play()print("開始吧")endfunction quit() print("你走吧");endrequire("game")game.setLevel(10)print(game.getLevel())

而package.seeall參數的作用就是讓原來的

_G依然生效,相當於調用了:

setmetatable(M, {__index = _G});

再次留意一下,代碼末尾的return M

也不見了,因為module函數的存在,

已經不需要我們主動去返回這個模塊的table了。

四:Lua面向對象簡單實現

1.隱藏參數self

在Lua裡面,我們定義一個函數的時候,可以使用如下的方式:

TSprite={ x = 0, y = 0}

function TSprite.setPosition(self, x, y)

self.x = x;

self.y = y;

end

local who = TSprite;

TSprite = nil;

who.setPosition(who,1, 2)

當我們調用setPosition() 的時候,我們將第一個參數 也就是當前函數的調用者傳 遞進去。這樣看起來比較麻 煩,因此Lua為我們提供了 一種解決方案,如下所示:

TSprite = { x = 0, y = 0, }

function TSprite:setPosition(x, y)

self.x = x; self.y = y;

end

local who = TSprite;

TSprite = nil;

who:setPosition(1, 2);

冒號的作用就是:定義函數時,給函數的添加隱藏的第一個參數self; 調用函數時,默認把當前調用者作為第一個參數傳遞進去。

2. 繼承實現

我們下面的代碼實現了繼承的功能,具體如下所示:

Hero = {attack=0} function Hero:new(o) o = o or {} setmetatable(o,self) self.__index = self return o end function Hero:skill(addAttack) self.attack = self.attack + addAttack end oneHero = Hero:new({attack=100}) oneHero:skill(10)

分析:當我們調用Hero:new的 時候,產生了一個新的表,並且 對應的元表是Hero,調用skill 函數的時候,在oneHero裡面無 法找到skill,就會去元表裡面 查找,查找到之後進行調用。

繼續往下,我們對oneHero進行對應的擴展如下,繼續輸入下面的代碼 :

function oneHero:test() print("test"); end function oneHero:injured(loseAttack) if loseAttack > self.attack then error"not engouth attack" end self.attack = self.attack - loseAttack/2 end oneHero:injured(100) print(oneHero.attack) -->60 oneHero:test()

我們重新聲明了函數injured,相當於覆蓋了原來table裡面的,當我們調用的時候,會調用我們新創建的,相當於C++當中的覆蓋。

Lua的面向對象我們就簡單的介紹到這裡,希望有興趣的同學更加深入研究。

五:Lua面向對象高級

為了更加方便的進行面向對象的編程設計,我們引入了一個成熟的工具類functions.lua,主要用來進行面向對象的Lua編程。class的內部實現如下所示:

function class(classname, super) local superType = type(super) local cls -- inherited from Lua Object if super then cls = {} setmetatable(cls, {__index = super}) cls.super = super else cls = {ctor = function() end} end cls.__cname = classname cls.__ctype = 2 -- lua cls.__index = cls function cls.new(...) local instance = setmetatable({}, cls) instance.class = cls instance:ctor(...) return instance end return clsend

推薦閱讀:

Community Help Sydney-
給unity新手找不到工作的人傳授幾招
U3D常用的類和結構體

TAG:Lua | Unity遊戲引擎 | unity |