这个问题在这里已经有了答案:
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++ 中很重要,原因有两个:
让我们依次解决这些问题。
首先,第二版代码的问题之一是您执行以下操作:
Ninja ninja;
Monster monster;
Enemy enemies[2];
enemies[0] = monster;
enemies[1] = ninja;
让我们来看看这里发生了什么。这首先创建了一个新的
Ninja
和 Monster
对象,然后创建一个数组 Enemy
对象,最后分配 enemies
数组 ninja
的值和 monster
.这段代码的问题是,当你写
enemies[0] = monster;
lhs 的静态类型是
Enemy
而rhs的静态类型是Monster
.在确定如何进行赋值时,C++ 只查看对象的静态类型,而不查看动态类型。这意味着因为 enemies[0]
静态类型为 Enemy
,它必须精确地保存类型为 Enemy
的东西,从来没有任何派生类型。这意味着当您执行上述赋值时,C++ 将其解释为“获取 monster
对象,仅识别其中的一部分 Enemy
,然后将该部分复制到 enemies[0]
中。”换句话说,虽然 Monster
是 Enemy
加上一些额外的添加,只有 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*
,它可以指向 Enemy
或 Enemy
的子类型.因此,C++ 将函数分派(dispatch)给对象的动态类型。希望这可以帮助!
关于c++ - 为什么在这种情况下需要指针?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6891566/