2015-12-19 54 views
1

我目前正在從「C++通過遊戲編程」的例子,我來到這個例子證明多態性使用基類指針到派生類對象

#include <iostream> 

using namespace std; 

class Enemy 
{ 
public: 
    Enemy(int damage = 10); 
    virtual ~Enemy(); 
    void virtual Attack() const; 

protected: 
    int* m_pDamage; 
}; 

Enemy::Enemy(int damage) 
{ 
    m_pDamage = new int(damage); 
} 

Enemy::~Enemy()    
{ 
    cout << "In Enemy destructor, deleting m_pDamage.\n"; 
    delete m_pDamage; 
    m_pDamage = 0; 
} 

void Enemy::Attack() const 
{ 
    cout << "An enemy attacks and inflicts " << *m_pDamage << " damage points."; 
} 

class Boss : public Enemy 
{ 
public: 
    Boss(int multiplier = 3); 
    virtual ~Boss(); 
    void virtual Attack() const; 

protected: 
    int* m_pMultiplier; 
}; 

Boss::Boss(int multiplier) 
{ 
    m_pMultiplier = new int(multiplier); 
} 

Boss::~Boss()     
{ 
    cout << "In Boss destructor, deleting m_pMultiplier.\n"; 
    delete m_pMultiplier; 
    m_pMultiplier = 0; 
} 

void Boss::Attack() const 
{ 
    cout << "A boss attacks and inflicts " << (*m_pDamage) * (*m_pMultiplier) 
     << " damage points."; 
} 

int main() 
{ 
    cout << "Calling Attack() on Boss object through pointer to Enemy:\n"; 
    Enemy* pBadGuy = new Boss(); 
    pBadGuy->Attack(); 

    cout << "\n\nDeleting pointer to Enemy:\n"; 
    delete pBadGuy; 
    pBadGuy = 0; 

    return 0; 
} 

我的問題是,爲什麼這條線使用:的

Enemy* pBadGuy = new Boss() 

代替

Boss badGuy; 
badGuy.Attack(); 

作者稱之爲「使用babes類指針派生類對象」。 經常使用嗎?它對於「常規」實例化方法有什麼優點嗎?

謝謝!

+0

可能重複[爲什麼我應該使用指針而不是對象本身?](http://stackoverflow.com/questions/22146094/why-should-i-use-a-pointer-rather-比對象本身) –

+0

幫你一個忙,找一本不同的書來學習C++編程。這個代碼示例有很多不對的地方,你只需要忘記。像'using namespace std;'一樣,那些指向單個'int'(可能剛剛使用'int')的'int *',在指針離開之前將一個指針指定給0(浪費時間);所有那些毫無意義的保護成員。 –

+0

我明白你的觀點,我也一直在閱讀其他書籍。我不明白爲什麼將0分配給一個指針。我認爲這本書比別人更好地解釋了一些概念(比如類,向量,指針和引用)。我也在閱讀Prata,但它在很多地方得到了非常詳細的介紹,並且還有一些重點從主題中解脫出來。 – Sabyc90

回答

2

這給出了一個虛擬方法如何工作的例子。即使你調用基類的方法,它也是被調用的子類的方法。

您提出的替代方案沒有清楚地證明這一關鍵概念。

兩種替代方案都可以完成同樣的事情,但這是爲了給出一個虛擬方法調度的例子。

+0

謝謝!這使它很清楚! – Sabyc90

0

想想以下幾點:你有一個錘子和鋸子。他們都是工具。他們在袋子裏(如矢量),如果你想使用它們,你從包裏取出一個,然後使用它(調用對象的方法)。你不能把錘子放在鋸袋裏,反之亦然,但是你可以把它們放在一個叫做工具的袋子裏。擁有一個包而不是兩個包並不容易。如果你想使用它們,你可以以適當的方式使用它(調用虛擬方法)。 所以你的敵人可以用多態性處理,而不用。此外,您還可以創建方法,如

// in your class... 
virtual void attackedByOther(const Enemy& other); 

在這種情況下,你只需要一個函數實現多次,這確實傷害計算等

所以回答你的問題:如果你使用指針,你可以做多態,但實例你不能。

我希望你明白!

0

我想補充一點什麼其他人說......

一個使用抽象類和虛函數的主要原因,是爲了讓您可以在同一陣列中有多個對象類型。

下面是一個項目,我在學校做了一個例子:

  char buffer = '\0'; 
      int itemindex = 0; 
      Item* myitem;//.................................. I used a polymorphic pointer Item 

      while (!myfile.get(buffer).fail()){//............ Check for failure each loop iteration 

       if (buffer == 'N' || buffer == 'P') { 

        if (_noOfItems - 1 >= itemindex) {//.... -1 because of index 0 and >= to account for the first set of entries 
         delete _items[itemindex];//.......... if it is >= than there has already been allocation at that index, so it must be freed 

        }//...................................... to avoid memory leak 
        if (buffer == 'P') { 
         myitem = new Perishable();//......... polymorphic pointer static type is now perishable (dynamic will always be item) 

        } else if (buffer == 'N') {//............... else is extra safe 
         myitem = new NonPerishable();//....... polymorphic pointer static type is now nonPerishable (dynamic is item) 

        } 

        myfile.ignore();//....................... ignore comma 
        myitem->load(myfile); 

        _items[itemindex] = myitem;//............ This line assigns myitem to the item index, since its polymorphic, only have to write 
               //............. it once, within the 'N' || 'P' scope. 
        itemindex++;//........................... This must incriment every time 'N' || 'P' is encountered, cause each represents an 
       }//.......................................... item entry. 

      } 

我們所要做的就是創建容易變壞,不易腐爛物品。如果你遵循註釋,你會看到我創建了一個Item(基類)指針,然後根據正在讀取的文件,如果char是'P',我創建了一個Perishable對象(派生)或NonPerishable對象(也派生) 。

所以這裏的關鍵,是_items[itemindex] = myitem;只調用一次,而不是在每一個條件分支buffer = P/N

有一些有趣的東西在這個例子中事,但正如我所說,無論是易腐(孩子)和NonPerishable(子)在Item(父)數組中。因此,如果我們正在處理老闆(孩子)和角色(孩子),他們都是實體(父),你可以通過你的實體(父)數組包含兩個派生類型,並調用相同的功能..像這樣的東西。

for(int i = 0; i < entityList.length; i++){ 
    entityList[i].attack 
} 

現在是/真的/很酷。你可以告訴所有的實體在同一行中做同樣的事情,所有這些都是因爲它們具有相同的父類型。

雖然你真的需要知道的是,已經提到的關於動態調度的內容。我向他解釋的方式很簡單:一個對象可以有一個動態類型和一個靜態類型。

動態類型是參考用,創建這樣的類型:
Item* myitem //Dynamic type is Item

靜態類型是什麼,指針/現在/分。所以現在靜態類型的myitem也是類型Item。

如果我這樣做:

myitem = new NonPerishable();

myitem指針正指向了孩子式 '不易腐爛'。動態類型不會更改,因爲它是作爲Item創建的。所以動態類型是/ still/type Item。但是現在靜態類型是NonPerishable,因爲Item指針(myitem)現在指向一個NonPerishable對象。

注:動態是它被創造(這可能是在名稱直覺,但它是如此)

最後,如果你有一個父類和子類都具有的功能相同的名稱,但不同的實現,你會得到早期綁定或後期綁定(又名動態調度)。

早期綁定意味着被觸發的函數將是父函數,動態調度意味着被觸發的函數將是子函數。早期綁定是C++的默認設置,爲了獲得動態分派,您必須將函數聲明爲「虛擬」,然後默認爲子函數。

您可以重寫綁定的一種方法是顯式聲明命名空間。這裏有一個例子:

class Parent; //pseudo code 
class Child : public Parent 

object.Child::myfunction() 

注:通常情況下,如果父母與子女有相同的功能(myfunction的),這將是早期綁定(母版)。在這種情況下,您使用child :: namespace,因此它會調用myfunction的子版本。

這是很多的信息,如果你有任何問題,我可以詳細說明。

0

我只是想詳細說明@SamVarshavchik說了些什麼,並以順序的方式回答你的問題。

  1. 這種特殊的聲明已被使用,因爲這有助於我們利用 動態綁定和使用的虛函數的概念。其中每一個的詳細信息都可以在網上找到。簡而言之, 動態綁定是在運行時而不是在編譯期間將對象綁定到 函數的概念,虛擬 函數是那些支持動態綁定的函數。而且,動態綁定只能使用指針和引用來執行,而不能使用對象本身來執行,而不能使用 ,因爲可以使用指向基類的指針 作爲指向派生類的指針,我們使用給定的聲明。

  2. 「是否經常使用?」是的,它經常使用,每當涉及多態性和繼承概念時更是如此,因爲它不是每次都可能預先知道用戶 意圖調用相應函數的哪個類。

  3. 我認爲現在我已經回答了您的最後一個問題,正如我所說的,因爲動態綁定可以使用指針或引用基類來執行,而不需要使用給定方法的對象。

+0

感謝您的回答!問題是,很多書和在線示例都提到「引用也可以使用」,但是沒有使用引用而是指針的例子。 – Sabyc90

相關問題