在引用上调用虚函数

时间:2013-05-19 14:05:03

标签: c++ inheritance virtual

在下面的代码中,为什么的最后一次调用 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.

5 个答案:

答案 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引用的对象。由于ac是等效的,因此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;
}