1. 程式人生 > >(轉載)【笨木頭Lua專欄】基礎補充20:面向對象——類和繼承

(轉載)【笨木頭Lua專欄】基礎補充20:面向對象——類和繼承

笑話 ava span 生成 code BE 手機 情況 忽略

終於來了,在Lua中的面向對象編程,相信目前學習Lua的大部分人都是為了開發手機網遊吧。
而且基本都是奔著腳本語言的熱更新特性去的,所以全腳本開發變得十分流行。

對於普及不太廣的Lua(相對於C++、Java等主流語言),需要短時間上手開發遊戲,對新手而言不算簡單。
所以大家才更習慣於繼續用面向對象思想去折騰Lua吧~

好了,不嘮叨了,我最不喜歡嘮叨了。(小若:是是是,你一點都不嘮叨,趕緊開講!)

笨木頭花心貢獻,哈?花心?不,是用心~

轉載請註明,原文地址:http://www.benmutou.com/archives/1791

文章來源:笨木頭與遊戲開發

1.類的對象

至於如何創建一個類,大家已經很清楚了,就是一個table而已。

那麽,要使用這個類去創建多個對象,又如何實現呢?

使用元表和元方法即可。

如下代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
TSprite = {
x = 0,
y = 0,
}
function TSprite:setPosition(x, y)
self.x = x;
self.y = y;
end

function TSprite:new()
o = {}
setmetatable(o, {__index = self});
return o;
end

local who1 = TSprite:new();
local who2 = TSprite:new();

who1:setPosition(1, 2);
who2:setPosition(44, 6);
print("who1坐標(" .. who1.x .. "," .. who1.y .. ")");
print("who2坐標(" .. who2.x .. "," .. who2.y .. ")");

留意TSprite的new函數,函數裏創建了一個新的table,並且給新的table設置一個元表,這個元表的__index元方法就是TSprite本身,最後返回這個新的table。

於是,所有通過new生成的新table,都可以使用TSprite的函數和各個字段屬性(因為__index的值是TSprite)。

因此,我們利用new函數創建了who1和who2,並且調用它們的setPosition函數,最後,who1和who2的x、y值都是不同的。

這就是類的對象了。

2.類對象的__index都是同一個TSprite,為什麽x、y值可以不相同?

不知道大家有沒有這樣一個疑惑,那就是,為什麽who1和who2的x、y是不一樣的,它們最終調用的不是setPosition函數麽?調用self.x時最終不是調用了TSprite的x值麽?

這裏是會有點混亂,理一理就沒問題了:

1). 當who1裏不存在setPosition時,回去__index元方法裏查找,於是,會找到TSprite的setPosition函數

2). 在setPosition函數裏,使用了self.x = x,此時的self就是who1,who1中是不存在x字段的,所以,如果我們要打印self.x的值,則其實是打印了TSprite的x值

3). 但是,註意,但是來了。__index元方法是用於調用的,而不是用於賦值的,因此,self.x = x這句話,其實只是給who1這個table的x字段賦值了,who1本身不存在x字段,此時給它賦值了,於是who1存在了x字段,以後who1都不會再去TSprite裏查找x字段了。

4). 因此,對who1和who2的x、y字段進行賦值操作時,是完全不會影響到TSprite的。

3.節省資源——使用TSprite作為元表

我們再仔細觀察一下new函數,我們在給新table設置元表的時候,是重新創建了一個元表的:setmetatable(o, {__index = self});

這麽做的話,每次調用new函數創建一個新對象時,都會產生一個新的元表,雖然這開支似乎可以忽略,但,擁有強迫癥的你,一定很喜歡下面的代碼:

1
2
3
4
5
6
function TSprite:new()
o = {}
setmetatable(o, self);
self.__index = self;
return o;
end

在這段新的new函數裏,使用self作為元表,然後又使用self作為__index的值。

這麽一看,有點繞不過來,我就喜歡大家繞不過來,這樣我又可以嘮叨了:

1). 調用new函數時,self其實就是TSprite本身,這裏完全可以用TSprite代替,不過,為了給以後做鋪墊,這裏還是使用self吧。

2). self.__index = self,不要被這句代碼嚇到了,其實還是那麽一回事,設置元表的__index元方法,這裏就 相當於TSprite.__index = TSprite。

3). TSprite自己作為__index的值沒問題麽?確實沒問題,TSprite也是一個table,table可以作為元表,元表可以有__index元方法,這絲毫沒有英雄。

4). 於是,通過這個小技巧,我們就避免了每次調用new函數時都額外創建一個新的元表了。

4.富二代什麽的我才不喜歡——繼承

我們總是笑話富二代,但誰的內心深處不希望自己是一個富二代呢~

像我這種立誌靠自己成為富一代的人,可不多了~(小若:啊我呸~!)

那麽,在Lua裏如何實現繼承呢?很簡單,但是需要認真思考,如下代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
TSprite = {
x = 0,
y = 0,
}
function TSprite:setPosition(x, y)
self.x = x;
self.y = y;
end

function TSprite:new()
o = {}
setmetatable(o, self);
self.__index = self;
return o;
end

local MoneySprite = TSprite:new();
function MoneySprite:setPosition(x, y)
print("呵呵,我是富二代,根本不需要改變。");
end

TSprite仍然沒變,但是,我們看看MoneySprite,按之前的理解,它是TSprite的一個對象。

只是,“對象”這稱呼是我們自己定的,實際上它還是一個table而已。

此時,我們修改了MoneySprite的setPosition函數,於是,調用MoneySprite的setPosition函數時,與TSprite無關了。

但,這不是重點,重點是接下來的代碼:

1
2
3
4
5
local who = MoneySprite:new();
who:setPosition(44, 6);

print("who坐標(" .. who.x .. "," .. who.y .. ")");

我們再次調用MoneySprite的new函數創建了一個新對象。

這又是什麽情況呢?關鍵是new函數裏的代碼,此時,new函數裏的self是誰?

new函數是由MoneySprite調用的,因此,self就是MoneySprite。

於是新對象的元表就是MoneySprite,元表的__index也是MoneySprite。

因此~!很神奇的,調用who的setPosition函數的時候,其實也是調用了MoneySprite的setPosition函數。

於是,who就是是MoneySprite的對象,而MoneySprite就是TSprite的子類。

來看看輸出結果吧:

[LUA-print] 呵呵,我是富二代,根本不需要改變。
[LUA-print] who坐標(0,0)

怎麽樣?繼承的實現方法也很簡單吧?

如果對元表、元方法、self比較生疏的話,可能一時間會理解不過來,沒關系,多思考一會,或者隔天再回頭思考,就會豁然開朗了。

5.結束

不知不覺這個系列的文章已經寫了20篇了,真是太出乎我的意料了。

我竟然可以堅持下來,但寫文章的效果確實很好,每晚的1個多小時付出也很值得。

起碼,我對Lua基礎的理解又更加鞏固了~

好吧,繼續堅持…(小若:所以說啊~!為什麽每次都要用省略號,用感嘆號不是更能表達你的決心嗎…)

原文地址:http://www.benmutou.com/archives/1791

(轉載)【笨木頭Lua專欄】基礎補充20:面向對象——類和繼承