使用基类指针派生类对象

时间:2015-12-19 18:44:05

标签: c++ pointers

我目前正在研究来自" 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();

作者称之为&#34;将宝贝类指针用于派生类对象&#34;。 经常使用吗?对于&#34;正常&#34;它有任何优势吗?实例化方法?

谢谢!

4 个答案:

答案 0 :(得分:2)

这给出了虚拟方法如何工作的示例。即使你正在调用基类的方法,它也是被调用的子类的方法。

您提出的替代方案并未明确证明这一关键概念。

两种替代方案都完成了相同的事情,但这是为了给出虚拟方法调度的一个例子。

答案 1 :(得分:0)

想想以下内容:你有锤子和锯子。他们都是工具。它们是袋子(如矢量),如果你想使用它们,你从袋子中取出一个,然后你使用它(调用对象的方法)。你不能把锤子放在锯子里。袋子,反之亦然,但你可以把它们放在一个叫做工具的包里。拥有一个袋子而不是两个袋子并不容易。如果要使用它们,请以适当的方式使用它(调用虚方法)。 所以你的敌人可以通过多态而更轻松地处理。此外,您可以创建方法,如

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

在这种情况下,您只需要执行一次更多次的功能,即进行损伤计算等。

所以回答你的问题:如果你使用指针你可以做多态,但是你可以使用实例。

我希望你理解!

答案 2 :(得分: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

在这个例子中有一些有趣的事情,但正如我所提到的,Perishable(child)和NonPerishable(child)都在Item(父)数组中。

因此,如果我们处理的是老板(子)和字符(子),并且它们都是实体(父级),则可以遍历包含两种派生类型的实体(父)数组,并调用相同的函数...像这样的东西。

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

现在是/真的/很酷。您可以告诉所有实体在一行中执行相同的操作,因为它们具有相同的父类型。

您真正需要知道的是,已经提到过有关动态调度的内容。向我解释的方法很简单:一个对象可以有一个动态类型和一个静态类型。

动态类型是创建引用的类型,如下所示:
    Item* myitem //Dynamic type is Item

静态类型是指针/当前/指向的内容。所以现在静态类型的myitem也是类型Item。

如果我这样做:

myitem = new NonPerishable();

myitem指针现在指向子类型'NonPerishable'。动态类型不会更改,因为它是作为Item创建的。所以动态类型是/ still / type Item。然而,静态类型现在是NonPerishable,因为Item指针(myitem)现在指向NonPerishable对象。

注意:动态是它被创建的(这在名称中可能是反直觉的,但情况确实如此)

最后,如果您有一个父类和子类,它们都具有相同名称但实现不同的函数,您将获得早期绑定或后期绑定(也称为动态调度)。

早期绑定意味着触发的函数将是父函数,动态调度意味着触发的函数将是子函数。早期绑定是C ++的默认设置,要获得动态调度,必须将该函数声明为“虚拟”,然后默认为子函数。

您可以覆盖绑定的一种方法是明确声明命名空间。这是一个例子:

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

object.Child::myfunction()

注意:通常,如果Parent和Child具有相同的功能(myfunction),则它将是早期绑定(父版本)。在这种情况下,您使用child :: namespace,因此它将调用myfunction的子版本。

如果您有任何问题我可以详细说明,这是很多信息。

答案 3 :(得分:0)

我将详细阐述@SamVarshavchik所说的内容,并按顺序回答您的问题。

  1. 此特定声明已被使用,因为这有助于我们利用 动态绑定和使用虚函数的概念。该 每个细节都可以在网上找到。简而言之, 动态绑定是对象绑定到的概念 在运行时而不是在编译期间运行,并且是虚拟的 函数是那些支持动态绑定的函数。而且动态 绑定只能用指针和引用来执行而不能 使用对象本身,因为可以使用指向基类的指针 作为派生类的指针,我们使用给定的声明。

  2. “经常使用吗?”是的,它经常被使用,当涉及多态性和继承的概念时更是如此,因为它不是 每次都可以预先知道用户所在的课程 打算召集相应的职能。

  3. 我想现在我已经回答了你的上一个问题,因为动态绑定既可以用指针执行,也可以用基类引用而不是对象,我们需要使用给定的方法。