Виртуальные функции - указатель базового класса - PullRequest
3 голосов
/ 23 марта 2012

Я понял, почему указатель базового класса делается для указания на объект производного класса.Но я не понимаю, почему мы должны назначать ему объект базового класса, если он сам по себе является объектом базового класса.

Может кто-нибудь объяснить это?

#include <iostream>
using namespace std;
class base {
     public:
        virtual void vfunc() {
            cout << "This is base's vfunc().\n";
         }
};
 class derived1 : public base {
      public:
            void vfunc() {
                 cout << "This is derived1's vfunc().\n";
    }
};
int main()
{
     base *p, b;
     derived1 d1;
     // point to base
     p = &b;
     p->vfunc(); // access base's vfunc()
     // point to derived1
     p = &d1;
     p->vfunc(); // access derived1's vfunc()
     return 0;
}

Ответы [ 4 ]

3 голосов
/ 23 марта 2012

Потому что сами указатели ничего не могут сделать.
Указатель должен указывать на действительный объект, чтобы вы могли использовать его любым способом.

Почему вышеприведенное утверждение?

Пошаговое объяснение, возможно, очистит ваше сомнение.

Шаг 1:

base *p;

Создает указатель p, который может хранить адрес объекта класса base. Но он не инициализируется, он указывает на любой случайный адрес в памяти.

Шаг 2:

p = &b;

Назначает адрес действительного base объекта указателю p. p теперь содержит адрес этого объекта.

Шаг 3:

p->vfunc(); // access base's vfunc() 

Разыменовывает указатель p и вызывает метод vfunc() на объекте, указанном им. т.е.: b.

Если вы удалите Шаг 2: , ваш код просто попытается разыменовать неинициализированный указатель и вызовет Неопределенное поведение и, скорее всего, сбой.

1 голос
/ 23 марта 2012

Я не полностью уверен, что прекрасно понимаю ваш вопрос, но типичный случай использования виртуальной функции - это когда мы пишем функцию и хотим, чтобы она работала собъект базового класса или что-либо производное от него:

struct base { 
    virtual void dosomething() = 0;
};

void myfunc(base *b) {
    b->dosomething();
}

Когда мы пишем myfunc, мы не знаем и не заботимся о точной идентичности вовлеченного объекта - мы просто заботимся о том,знает, как dosomething выполнять по команде.

Что касается написания кода, как вы показали, где мы напрямую присваиваем адреса объектов базового или производного класса указателю, то естьскорее исключение, чем правило.Вы совершенно правы, что в подобной ситуации мы не очень выигрываем от использования указателя на базу для ссылки на объект производного.Основным преимуществом является нечто вроде функции, в которой производный класс может даже не существовать, когда мы пишем код, но до тех пор, пока производный класс соответствует предписанному интерфейсу, он будет работать.

1 голос
/ 23 марта 2012

Необязательно звонить

p = &b;
p->vFunc();

, вы можете напрямую позвонить

b.vFunc();

, и то и другое даст вам одинаковый результат.

Однако, похоже, вам следуетпонять силу виртуальных функций.Предположим, что если вы хотите сохранить 10 экземпляров объектов base или derived1 и повторно вызывать функцию, как вы это сделаете?или после сохранения в массиве, если вы хотите передать его в общую функцию?

vase *p[4];

base b1;
derived d;

p[0] = new base();

p[1] = &b1;

p[2] = new dervied1();    

p[3] = &d;

for (int i =0 ;i <4 ;i++)
{
  p[i]->vFunc();
}
0 голосов
/ 23 июня 2012

Этот тип ссылок очень хорошо применяется, когда вы хотите применить шаблоны проектирования (вам может потребоваться пройти продвинутый курс объектно-ориентированного проектирования или начать с чтения книги: голова в первую очередь, шаблоны проектирования, я предлагаю)

см., Например, как реализовать шаблон декоратора в Java в упомянутой книге.

public abstract class Beverage {
String description = "Unknown Beverage";
public String getDescription() {
    return description;
}
public abstract double cost();   
}

тогда подумайте, что у нас есть эспрессо и DarkRoast как два дочерних класса:

public class Espresso extends Beverage {

public Espresso() {
    this.description = "Espresso";
}

public double cost() {
    return 1.99;
}
}

public class DarkRoast extends Beverage {
public DarkRoast() {
    description = "Dark Roast Coffee";
}

public double cost() {
    return .99;
}
}

Теперь мы добавим декоратор:

public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
 }

и постройте бетонный декоратор:

public class Mocha extends CondimentDecorator {

Beverage beverage;

public Mocha(Beverage beverage) {
    this.beverage = beverage;
}

public String getDescription() {
    return beverage.getDescription() + ", Mocha";
}

public double cost() {

    return .20 + beverage.cost();
}
} 

и другая обертка:

public class Whip extends CondimentDecorator {
Beverage beverage;

public Whip(Beverage beverage) {
    this.beverage = beverage;
}

public String getDescription() {
    return beverage.getDescription() + ", Whip";
}

public double cost() {
    return .10 + beverage.cost();
}
}

наконец, посмотрите, что произошло в основной функции и как мы используем преимущества указателя на класс отца:

public static void main(String[] args) {
    Beverage beverage = new Espresso();
    System.out.println(beverage.getDescription() + " $" + beverage.cost());
    Beverage beverage2 = new DarkRoast();
    beverage2 = new Mocha(beverage2);
    beverage2 = new Mocha(beverage2);
    beverage2 = new Whip(beverage2);
    System.out.println(beverage2.getDescription() + " $" + beverage2.cost());

Вы можете догадаться, что вывод? хорошо:

Espresso $1.99
Dark Roast Coffee, Mocha, Mocha, Whip $1.49
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...