2013-05-19 4 views
1

В следующем коде, почему последний вызов поесть() по ссылке с возвращение «животное ест б.»? В моем понимании, с является ссылкой на экземпляр б производного класса собак и едят() виртуальная функция. Поэтому он должен был вернуться «Собака ест».Вызов виртуальной функции по ссылке

#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. 
+1

Я не вижу ссылочный 'd' в коде. – taocp

+0

@taocp, извините, это c, а не d. Я уже это исправил. –

+0

3 ответа в течение 12 секунд .... :) – dyp

ответ

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; 
} 
+0

Большое спасибо за разъяснение. –

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), чей динамический тип Animal - тот же самый тип, как a - поэтому Animal::eat вызывается вместо Dog::eat.

+0

Большое спасибо за объяснение в деталях. –

3
Animal & c = a; 
c.eat(); 

c = b; ///^^^ 
c.eat(); 

В C++ ссылка не может быть привязана к другому объекту после его инициализации. c по-прежнему является псевдонимом объекта a, который является Animal, поэтому вы видели ожидаемый результат.

+0

Большое спасибо. :) –

+0

@takwing вы радушны! – taocp

3

Ссылка на объект псевдоним. После привязки ссылки к объекту (и что должен произойти во время инициализации), то, что вы делаете по ссылке, делается на объекте, на который делается ссылка.

В частности, вы не можете повторно связать ссылку, которая уже привязана к объекту и позволяет ссылаться на другой объект.Таким образом, следующее задание (потому, что приведен назначение, а не инициализации):

c = b; 

Is эквивалентно следующему:

a = b; 

Поскольку c представляет собой ссылку на объект a. Вышеуказанное присваивание приводит к slicing, которое не то, что вы хотели: c не будет ссылкой, привязанной к b, но она по-прежнему будет привязана к a, к которой назначено b.

+0

Что означает термин slicing в данном конкретном контексте? Означает ли это, что только часть «Животных» объекта производного класса «Собака» копируется в? –

+0

@takwing: Да, действительно. Также см. [Эту статью] (http://en.wikipedia.org/wiki/Object_slicing) для более подробного объяснения –

+0

большое спасибо за ссылку. –

1

Вы не можете повторно связать ссылки, как только вы связали его, так что вы должны использовать указатели вместо ссылок:

Animal *c = &a; 
c->eat(); 

c = &b; 
c->eat(); 

Теперь он будет работать именно так, как вы хотели его.

+0

Большое спасибо за ваш ответ :) –

Смежные вопросы