繁体   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