簡體   English   中英

使用基類指針派生類對象

[英]Using Base Class Pointers to Derived Class Objects

我目前正在研究“通過游戲編程進行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();

作者稱其為“將寶貝類指針用於派生類對象”。 它經常使用嗎? 與“常規”實例化方法相比,它有什么優勢嗎?

謝謝!

這給出了虛擬方法如何工作的示例。 即使您正在調用基類的方法,也將調用子類的方法。

您提議的替代方案沒有清楚地說明這個關鍵概念。

兩種選擇都可以完成相同的事情,但這僅是給出虛擬方法分派的示例。

考慮以下問題:您有一把錘子和鋸子。 它們都是工具。 它們放在袋子中(如矢量),如果要使用它們,請從袋子中取出一個,然后使用它(調用對象的方法)。 您不能將錘子放在鋸子的袋子中,反之亦然,但是可以將錘子放在稱為工具的袋子中。 擁有一個袋子而不是兩個袋子並不容易。 如果要使用它們,請以適當的方式使用它(調用虛擬方法)。 因此,使用多態可以輕松處理您的敵人,而無需處理。 另外,您可以創建方法,例如

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

在這種情況下,您只需要將一個函數實現多次,即可進行損害計算等。

因此回答您的問題:如果使用指針,則可以進行多態操作,但是使用實例則不能。

我希望你明白!

只是為了增加其他人的發言...

使用抽象類和虛函數的主要原因之一是使您可以在同一數組中擁有多個對象類型。

這是我在學校所做的一個項目示例:

            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

在此示例中,發生了一些有趣的事情,但是正如我提到的,易腐性(子級)和非易腐性(子級)都在Item(父級)數組中。

因此,如果我們要處理老板(子)和角色(子),並且它們都是實體(父),則可以遍歷包含兩種派生類型的實體(父)數組,並調用相同的函數...類似這樣。

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

現在/真的/很酷。 您可以告訴所有實體在同一行中執行相同的操作,這都是因為它們具有相同的父類型。

但是,您真正需要知道的是有關動態調度的內容。 向我解釋的方法很簡單:對象可以具有動態類型和靜態類型。

動態類型是創建引用的類型,如下所示:
Item* myitem //Dynamic type is Item

靜態類型是指針/ currently /指向的類型。 因此,現在myitem的靜態類型也是Item類型。

如果我這樣做:

myitem = new NonPerishable();

myitem指針現在指向子類型“ NonPerishable”。 動態類型不會更改,因為它是作為項目創建的。 所以動態類型是/ still /類型Item。 但是,靜態類型現在是Nonperishable對象,因為Item指針(myitem)現在指向了NonPerishable對象。

注意:動態就是它的創建方式(名稱可能很直觀,但是確實如此)

最后,如果您的父類和子類都具有相同名稱的函數,但實現方式不同,則將獲得早期綁定或后期綁定(也稱為動態調度)。

早期綁定意味着將觸發的功能將是父功能,動態調度意味着將觸發的功能將是子功能。 早期綁定是C ++的默認設置,要進行動態分派,必須將函數聲明為“虛擬”,然后默認設置為子功能。

覆蓋綁定的一種方法是顯式聲明名稱空間。 這是一個例子:

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

object.Child::myfunction()

注意:通常,如果“父級”和“子級”具有相同的功能(“我的功能”),它將被早期綁定(“父級”版本)。 在這種情況下,您使用child ::命名空間,因此它將調用myfunction的子版本。

如果您有任何疑問,我可以提供很多信息。

我將詳細說明@SamVarshavchik所說的內容,並按順序回答您的問題。

  1. 使用此特定聲明是因為這有助於我們利用動態綁定和使用虛函數的概念。 可以在網上找到有關每一個的詳細信息。 簡而言之,動態綁定是在運行時而不是在編譯時將對象綁定到函數的概念,而虛函數是支持動態綁定的函數。 此外,動態綁定只能用指針和引用執行,而不能用對象本身執行,並且由於指向基類的指針可以用作指向派生類的指針,因此我們使用給定的聲明。

  2. “它經常使用嗎?” 是的,它經常被使用,因此在涉及多態性和繼承的概念時會更多地使用它,因為不可能每次都事先知道用戶打算針對各個類調用哪個函數。

  3. 我認為到目前為止,我已經回答了您的最后一個問題,因為動態綁定可以使用指針或對基類的引用而不是對象的引用來執行,我們需要使用給定的方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM