c++ - 为什么在这种情况下需要指针?

标签 c++ arrays pointers polymorphism

这个问题在这里已经有了答案:




10年前关闭。




Possible Duplicate:
Learning C++: polymorphism and slicing



这是建立在我之前问过的一个问题之上的。
这些类看起来像这样:
class Enemy
{
    public:
        void sayHere()
        {
            cout<<"Here"<<endl;
        }
        virtual void attack()
        {
        }
};

class Monster: public Enemy
{

    public:
        void attack()
        {
            cout<<"RAWR"<<endl;
        }

};
class Ninja: public Enemy
{

    public:
        void attack()
        {

            cout<<"Hiya!"<<endl;
        }
};

我是 C++ 的新手,我很困惑为什么这只适用于指针(Ninja 和 Monster 都源自 Enemy):
int main()
{
    Ninja ninja;
    Monster monster;

    Enemy *enemies[2];

    enemies[0] = &monster;
    enemies[1] = &ninja;

    for (int i = 0; i < 2; i++)
    {
        enemies[i]->attack();
    }

    return 0;
}

为什么我不能这样做?:
int main()
{
    Ninja ninja;
    Monster monster;

    Enemy enemies[2];

    enemies[0] = monster;
    enemies[1] = ninja;

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

    return 0;
}

最佳答案

这是一个很好的问题,它触及了 C++ 继承的一些棘手问题的核心。混淆是由于 之间的差异引起的静态类型 动态类型 ,以及 C++ 为对象分配存储的方式。

首先,让我们讨论静态和动态类型之间的区别。 C++ 中的每个对象都有一个静态类型,即源代码中描述的对象的类型。例如,如果您尝试编写

Base* b = new Derived;

然后是 b 的静态类型是 Base* ,因为在源代码中这是您为其声明的类型。同样,如果你写
Base myBases[5];
myBases的静态类型是 Base[5] , 5 个数组 Base s。

对象的动态类型是对象在运行时实际拥有的类型。例如,如果你写这样的东西
Base* b = new Derived;

然后是b的动态类型是 Derived* ,因为它实际上指向的是 Derived目的。

静态和动态类型之间的区别在 C++ 中很重要,原因有两个:
  • 对对象的赋值总是基于对象的静态类型,而不是动态类型。
  • 如果静态类型是指针或引用,则虚函数的调用仅分派(dispatch)到动态类型。

  • 让我们依次解决这些问题。

    首先,第二版代码的问题之一是您执行以下操作:
    Ninja ninja;
    Monster monster;
    
    Enemy enemies[2];
    
    enemies[0] = monster;
    enemies[1] = ninja;
    

    让我们来看看这里发生了什么。这首先创建了一个新的 NinjaMonster对象,然后创建一个数组 Enemy对象,最后分配 enemies数组 ninja 的值和 monster .

    这段代码的问题是,当你写
    enemies[0] = monster;
    

    lhs 的静态类型是 Enemy而rhs的静态类型是Monster .在确定如何进行赋值时,C++ 只查看对象的静态类型,而不查看动态类型。这意味着因为 enemies[0]静态类型为 Enemy ,它必须精确地保存类型为 Enemy 的东西,从来没有任何派生类型。这意味着当您执行上述赋值时,C++ 将其解释为“获取 monster 对象,仅识别其中的一部分 Enemy,然后将该部分复制到 enemies[0] 中。”换句话说,虽然 MonsterEnemy加上一些额外的添加,只有 Enemy Monster 的一部分将被复制到 enemies[0]用这行代码。这叫做切片 ,因为您要切掉对象的一部分并只留下 Enemy基部。

    在您发布的第一段代码中,您有:
    Ninja ninja;
    Monster monster;
    
    Enemy *enemies[2];
    
    enemies[0] = &monster;
    enemies[1] = &ninja;
    

    这是完全安全的,因为在这行代码中:
    enemies[0] = &monster;
    

    lhs 有静态类型 Enemy*并且 rhs 的类型为 Monster* . C++ 合法地允许您将指向派生类型的指针转​​换为指向基类型的指针而不会出现任何问题。结果,rhs monster指针可以无损地转换为 lhs 类型 Enemy* ,因此对象的顶部不会被切掉。

    更一般地,当将派生对象分配给基础对象时,您可能会面临对对象进行切片的风险。将指向派生对象的指针存储在指向基对象类型的指针中总是更安全、更可取,因为不会执行切片。

    这里还有第二点。在 C++ 中,每当您调用虚函数时,如果接收者是指针或引用类型,则该函数只会在对象的动态类型(对象在运行时实际属于的对象类型)上调用。也就是说,如果您有原始代码:
    Ninja ninja;
    Monster monster;
    
    Enemy enemies[2];
    
    enemies[0] = monster;
    enemies[1] = ninja;
    

    和写
    enemies[0].attack();
    

    那么因为 enemies[0]有静态类型 Enemy ,编译器不会使用动态分派(dispatch)来确定 attack 的版本调用的函数。原因是如果对象的静态类型是Enemy ,它总是指一个 Enemy在运行时,没有别的。但是,在代码的第二个版本中:
    Ninja ninja;
    Monster monster;
    
    Enemy *enemies[2];
    
    enemies[0] = &monster;
    enemies[1] = &ninja;
    

    当你写
    enemies[0]->attack();
    

    然后因为enemies[0]有静态类型 Enemy* ,它可以指向 EnemyEnemy 的子类型.因此,C++ 将函数分派(dispatch)给对象的动态类型。

    希望这可以帮助!

    关于c++ - 为什么在这种情况下需要指针?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6891566/

    相关文章:

    c - 检查目录时出现指针问题

    c++ - 调用指向成员函数的模板指针

    c++ - 创建 LinkedList 退出并返回代码 -11 (SIGSEGV)

    c++ - 构造函数采用参数时的GTest夹具?

    c++ - C++类中的静态声明

    c++ - 有效获取字符串流的子流

    C++/MFC - 如何将窗口内容导出到 Excel?

    python - 将值移出对象

    arrays - 如何快速初始化我的数组

    arrays - 在二维数组中搜索元素