Переопределение виртуальных функций и наследования - PullRequest
0 голосов
/ 11 октября 2018

У меня возникают проблемы с полным пониманием переопределения виртуальных функций в C ++ и того, что именно происходит, когда такие функции вызываются. Я читаю PPP, используя C ++, Бьярна Страуструпа, и он приводит следующий пример для демонстрации переопределения и виртуальных функций:

struct B {
    virtual void f() const { cout<<"B::f; }
    void g() const { cout << "B::g"; }        //not virtual
};

struct D:B {
        void f() const { cout<<"D::f"; } //overrides B::f
        void g() { cout<<"D::g"; }
};

struct DD:D {
        void f() { cout<<"DD::f"; }
        void g() const { cout<<"DD::g";}    
};

void call(const B& b) {
    //a D is kind of B, so call() can accept D
    //a DD is kind of D and a D is a kind of B, so call() can accept a DD
    b.f();
    b.g();
}

int main() {
    B b;
    D d;
    DD dd;

    call(b);
    call(d);
    call(dd);

    b.f();
    b.g();
    d.f();
    d.g();
    dd.f();
    dd.g();
}

Вывод: B::f B::g D::f B::g D::f B::g B::f B::g D::f D::g DD::f DD::g

Я понимаю, как вызов (b) выводит B::f B::g, что просто.

Сейчас call(d).Я не совсем понимаю, почему, но, похоже, call() может принимать производные классы B в качестве аргумента.Хорошо.Таким образом, в call(), b.f() становится d.f(), поскольку D::f переопределяет B::f.На самом деле вывод говорит D::f.Но D::g не отменяет B::g, и по причинам, которые я не могу понять, мне кажется, что D::g не действует при выполнении call(d) - в этом случае выводит B::g.

Далее мы выполняем call(dd), который выдает D::f B::g.Применяя ту же логику (?), Что и выше, ясно, что DD::f не переопределяет D::f - не const - и DD::g не переопределяет ни D::g, ни B::g, поскольку ни virtual.

Что будет дальше, сбивает меня с толку.Каждый отдельный вызов b.f(), b.g(), d.f(), d.g(), dd.f(), dd.g() выводит результат, как если бы переопределение вообще не существовало!Например, как d.g() может выдать D::g, если всего несколько секунд назад d.g() в вызове () вывести B::g?Другой, как dd.f() может выводить DD::f, когда в dd.f() на call() выводится D::f?

Можно с уверенностью сказать, что я упускаю что-то большое здесь, и для этого мне нужна помощь.

Ответы [ 2 ]

0 голосов
/ 12 октября 2018

Например, как dg () может выводить D :: g, если всего несколько секунд назад dg () в call () выводит B :: g?

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

рассмотрим этот пример,

B *bp;
bp = &d;
bp->f();
bp->g();

при выполнении bp->g() будет вызвана g функция b.

0 голосов
/ 12 октября 2018

Пожалуйста, наберитесь терпения и прочитайте это, чтобы получить ответы на свои вопросы.Наследование - это концепция, в которой объект класса наследует свойства и поведение объекта другого класса.

  • Основное введениеРодительский класс также называется базовым классом или суперклассом.Дочерний класс также называется производным классом или подклассом
  • На объект производного класса можно ссылаться через базовый класс.

Например

#include <iostream>
using namespace std;
class Base {};
class Derived : public Base {};
int main() {
  Base *b = new Derived(); // This is completely valid
  return 0;
}
  • Переопределение метода в C ++ Давайте рассмотрим простой пример

#include <iostream>
using namespace std;
class Base {
public:
  void display() { cout << "Base display called\n"; }
};
class Derived : public Base {
public:
  void display() { cout << "Derived display called\n"; }
};
int main() {
  Base b;
  b.display();
  Derived d;
  d.display();
  Base *bptr = &d;
  bptr->display();
  return 0;
}
Output:
Base display called
Derived display called
Base display called

Теперь из приведенного выше примера вы могли догадаться, что производный класс переопределяет базовый класс, но это не так.Вывод (3-я строка) показывает, что вызывается функция базового класса.

  • Виртуальные функции в C ++

Вы можете сделать любую функцию класса виртуальной, добавив ключевое слово «virtual» в начале функции.Давайте рассмотрим пример виртуальной функции

#include <iostream>
using namespace std;
class Base {
public:
  virtual void display() { cout << "Base display called\n"; }
};
class Derived : public Base {
public:
  void display() { cout << "Derived display called\n"; }
};
int main() {
  Base b;
  b.display();
  Derived d;
  d.display();
  Base *bptr = &d;
  bptr->display();
  return 0;
}
Output:
Base display called
Derived display called
Derived display called

Из приведенного выше примера (строка 3 вывода) ясно, что вы можете добиться переопределения метода с помощью механизма виртуальной функции в C ++.

  • Какой эффект делает функцию виртуальной?

Разница между нормальной функцией и виртуальной функцией заключается в том, что нормальная функция разрешается во время компиляции, также известной как статическое связывание, тогда как виртуальная функция разрешается во время выполнения, также известной как динамическая.обязательное или позднее связывание.Какой метод вызывать (метод отображения базового класса или метод отображения производного класса) решается во время выполнения, поскольку функция отображения базового класса становится виртуальной.Вы можете углубиться в механизм виртуальных функций, читая о V-таблицы

Ответы на вопросы1) вызов (г).Я не совсем понимаю, почему, но кажется, что call () может принимать производные классы B в качестве аргумента.=> На объект производного класса может ссылаться базовый класс2) Но D :: g не переопределяет B :: g и по причинам, которые я не могу понять=> Потому что g () не является виртуальным в базовом классе.Только виртуальные функции могут быть переопределены.V-таблица имеет записи только для виртуальных функций, так что они могут быть переопределены во время выполнения.3) call (dd) => Когда вызывается функция f (), функция сначала ищется в базовом классе B, поскольку функция f () является виртуальной, используя указатель v-таблицы, причем функция f () переопределяется в производном классе.Д решен.Но поскольку функция f () не является виртуальной в классе D, переопределенная функция f () в DD не может быть разрешена.Следовательно, D :: f печатается.Но для g () сам базовый класс не имеет виртуального g (), поэтому переопределенные функции в их производных классах не могут быть разрешены.Следовательно, B :: g печатается.4) Вышеупомянутый полиморфизм произошел потому, что на производные классы ссылались их базовые классы (родительские классы), но в последних вызовах такого нет.На все объекты ссылаются их соответствующие классы, и, следовательно, вызываются их соответствующие методы.

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

...