在下面的代码中,为什么吃的最后一次调用 c 返回“ 动物b正在进食。< / em> “?根据我的理解, c 是对派生类狗的实例 b 的引用, eat()是虚函数。所以它本应该返回“狗b正在吃东西。”
#include <string>
#include <iostream>
using namespace std;
class Animal
{
protected:
string name;
public:
Animal( string _name ):
name(_name)
{
}
virtual void eat()
{
cout << "An animal " << name << " is eating." << endl;
}
};
class Dog : public Animal
{
public:
Dog( string _name ):
Animal(_name)
{
}
void eat()
{
cout << "A dog " << name << " is eating." << endl;
}
};
int main( int argc , char ** argv )
{
Animal a("A");
a.eat();
Dog b("b");
b.eat();
Animal & c = a;
c.eat();
c = b;
c.eat();
return 0;
}
这是输出:
An animal A is eating.
A dog b is eating.
An animal A is eating.
An animal b is eating.
答案 0 :(得分:3)
Animal & c = a;
c.eat();
c = b; ///^^^
c.eat();
在C ++中,一旦初始化引用,引用就不能重新绑定到其他对象。 c
仍然是对象a
的别名,它是Animal
,因此,您看到了预期的输出。
答案 1 :(得分:3)
引用是对象的别名。绑定对象的引用(并且必须在初始化时发生)后,对引用执行的操作在被引用的对象上完成。
特别是,您无法重新绑定已绑定到对象的引用并让它引用另一个对象。因此,以下分配(因为赋值,而不是初始化):
c = b;
等同于以下内容:
a = b;
由于c
是对象a
的引用。以上分配结果为slicing,这不是您想要的:c
不会成为b
的引用,但它仍然是a
的引用。 1}},已分配b
。
答案 2 :(得分:2)
因为您无法重新引用引用。初始化它们后,它们的名称始终引用您初始化它们的对象。
对象可以有一个名称,例如Animal a("A");
创建了一个Animal
类型的对象,并引入了一个引用此对象的名称a
。
另一方面,引用引入名称而不引入对象(让我们不考虑临时对象):
Animal& c = a; // a new name `c` which refers to the same object as `a`
// another (evil) example:
Animal& c = *(new Animal("C")); // `new` introduces an object without name
// `c` now refers to this object
关于作业:
Animal & c = a;
// the name `c` is now equivalent to the name `a`
c = b; // equivalent to `a = b;`
最后一个赋值使用b
引用的对象,并将其Animal
类型的子对象复制到c
引用的对象。由于a
和c
是等效的,因此a
指的是同一个对象。因此,a.name
设置为"B"
。
虚函数调用c.eat()
当然对动态类型为c
的id-expression(Animal
)进行操作 - 与a
的类型相同 - 因此,{调用{1}}而不是Animal::eat
。
答案 3 :(得分:1)
绑定后不能重新绑定引用,因此必须使用指针而不是引用:
Animal *c = &a;
c->eat();
c = &b;
c->eat();
现在它将完全按照你的意愿工作。
答案 4 :(得分:1)
为了利用虚函数提供的动态多态(在运行时区分派生类和基类),需要通过基类指针或引用访问派生类对象。
我已经注释掉了可能发生混淆的代码:
int main( int argc , char ** argv )
{
Animal a("A");
a.eat();
Dog b("b");
b.eat();
// Make a reference (alias) to Animal object and set it to the object a.
// From this point on, whenever you write c, think "a".
Animal & c = a;
// So, this is a.eat()
c.eat();
// This is a = b (Animal = Dog): DANGER! SLICING! Here, the assignment operator
// slices the derived object and only assigns the base object "part" (remember,
// read "a", where you see "c" in your code):
// a.operator=(const A& b)
c = b;
// a.eat() = a is object of type A, so naturally, here you call A::eat()
c.eat();
return 0;
}