同名異式還有一項重點,就是這種情況只會發生在繼承與虛擬函式上面,B、C 類別同樣繼承 A 又符合上面的條件,才能說 B、C 有同名異式,這就要來筆記一下 virtual 關鍵字了。
假設你要寫一個 RPG 遊戲,遊戲中有很多種類的 NPC例如蛇妖、蜘蛛、火龍、強盜等 NPC,這時我們可以先找出共通點建立一個叫 BaseNpc 的基底類別,
class CBaseNpc
{
private:
int m_nType; // 怪的屬性、攻擊型、魔法型等
int m_nHP; // 每種 npc 都有 HP 值
int m_nMP; // 每種 npc 都有 MP 值
int m_nDefense; // 防禦力
int m_nAttack; // 攻擊力
public:
int GetHP() {return m_nHP;};
void SetHP(int n) {m_nHP = n;};
int GetMP() {return m_nMP;};
void SetMP(int n) {m_nMP = n;};
int GetDefense() {return m_nDefense;};
void SetDefense(int n) {m_nDefense = n;};
int GetAttack() {return m_nAttack;};
void SetAttack(int n) {m_nAttack = n;};
};
這時我要做寫任何怪的類別時,都可以使用公用繼承省下寫這些重覆的 Code 例如
class CSnake : public CBaseNpc
{
// 使用 public 繼承,讓 Snake 類別已包含 BaseNpc 的公用介面,
// 但對於 BaseNpc 的私有成員必須透過公用函式來存取。
};
在實作CSnake時發現,這似乎不太夠,NPC 還要會攻擊呀,但蛇妖的攻擊是咬或噴毒、甩尾巴,強盜是砍、踢、丟暗器,同樣都有 Attack() 函式,我們就可以把這個 function 加到基底類別中,但各 NPC 的行為不同,我們在 function 定義前加上 virtual 關鍵字變成
class CBaseNpc
{
private:
int m_nType // 怪的屬性、攻擊型、魔法型等
int m_nHP; // 每種 npc 都有 HP 值
int m_nMP; // 每種 npc 都有 MP 值
int m_nDefense; // 防禦力
int m_nAttack; // 攻擊力
public:
int GetHP() {return m_nHP;};
void SetHP(int n) {m_nHP = n;};
int GetMP() {return m_nMP;};
void SetMP(int n) {m_nMP = n;};
int GetDefense() {return m_nDefense;};
void SetDefense(int n) {m_nDefense = n;};
int GetAttack() {return m_nAttack;};
void SetAttack(int n) {m_nAttack = n;};
virtual int Attack(int nHeroDef)
{
// 站著不動,僅回傳攻擊力減掉主角的防禦力/2
// 甚至加入更複雜的屬性相剋判斷或 miss 機率
return (m_nAttack - (nHeroDef / 2));
};
};
這時其他繼承 CBaseNpc 的怪就可以自己實作自己的攻擊方式,
例如蛇妖和強盜都實作了不同的攻擊方式
int main()
{
CSnake snakeA; // 一支蛇妖
CRobber robberA; // 一個強盜
CHero hero; // 主角
snakeA.Attack(hero.GetDefense()); // 對主角咬或噴毒、甩尾
robberA.Attack(hero.GetDefense()); // 拿兵器砍或丟主角
return TRUE;
}
整篇的重點來了,其實以上面的例子看來,沒有加 virtual 關鍵字也是可以的,CSnake 的物件會去做 CSnake 的攻擊動作,強盜會做強盜自己的攻擊動作,那我們沒事加個 virtual 做什麼呢?這要牽扯到物件的型態,在沒有 virtual 關鍵字的情況下,物件會根據它自己的型態去呼叫相對應的函式。
但注意,基底類別可以指向任何繼承它的衍生類別,這時它只能使用自己所有的類別成員,不可以使用衍生類別多出來的成員,且不論它指向什麼,一律只做基底類別的動作,因為它是基底類別指標嘛。
有種情況發生在我們常常希望用 "陣列" 來管理畫面中的所有 NPC,所以我們宣告一個可以指向所有NPC的基底類別陣列,若沒有用 virtual 關鍵字,這段程式碼的結果會變這樣。
int main()
{
CBaseNpc *arrayNPC(2);
CHero hero; // 主角
arrayNPC[0] = new CSnake();
arrayNPC[1] = new CRobber();
arrayNPC[0]->Attack(hero.GetDefense()); // 執行基底類別的"站著不動"
arrayNPC[1]->Attack(hero.GetDefense()); // 執行基底類別的"站著不動"
return TRUE;
}
要解決這種情況的發生,virtual 就派上用場了,若你需要用到基底類別的物件指標指向任何 NPC 時,要記得將這類函式加上 virtual 關鍵字,才能正確執行所指類別型態的 function
沒有留言:
張貼留言