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
推薦閱讀: