Подтвердить что ты не робот

Переопределение виртуальных

Какова цель использования зарезервированного слова virtual перед функциями? Если я хочу, чтобы дочерний класс переопределял родительскую функцию, я просто объявляю ту же функцию, что и void draw(){}.

class Parent { 
public:
    void say() {
        std::cout << "1";
    }
};

class Child : public Parent {
public:
    void say()
    {
        std::cout << "2";
    }
};

int main()
{
    Child* a = new Child();
    a->say();
    return 0;
}

Выходной сигнал равен 2.

Итак, почему зарезервированное слово virtual необходимо в заголовке say()?

Спасибо, куча.

4b9b3361

Ответ 1

Это классический вопрос о том, как работает полиморфизм, я думаю. Основная идея заключается в том, что вы хотите абстрагировать конкретный тип для каждого объекта. Другими словами: вы хотите иметь возможность вызывать экземпляры Child, не зная, что это child!

Вот пример: предполагая, что у вас есть класс "Child" и класс "Child2" и "Child3", вы хотите иметь возможность ссылаться на них через их базовый класс (Parent).

Parent* parents[3];
parents[0] = new Child();
parents[1] = new Child2();
parents[2] = new Child3();

for (int i=0; i<3; ++i)
    parents[i]->say();

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

Ответ 2

Если функция была виртуальной, то вы могли бы сделать это и все равно получить вывод "2":

Parent* a = new Child();
a->say();

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

Ответ 3

Попробуйте:

Parent *a = new Child();
Parent *b = new Parent();

a->say();
b->say();

Без virtual, как с печатью '1'. Добавьте virtual, и ребенок будет действовать как дочерний элемент, хотя он ссылается с помощью указателя на Parent.

Ответ 4

Если вы не используете ключевое слово virtual, вы не переопределяете, а устанавливаете неверный метод, не связанный с производным классом, который скроет метод базового класса. То есть без virtual, Base::say и Derived::say не связаны между собой, кроме совпадения имени.

Когда вы используете ключевое слово virtual (обязательно в базе, необязательно в производном классе), вы сообщаете компилятору, что классы, которые происходят из этой базы, смогут переопределить метод. В этом случае Base::say и Derived::say рассматриваются как переопределения того же метода.

Когда вы используете ссылку или указатель на базовый класс для вызова виртуального метода, компилятор добавит соответствующий код, чтобы вызывать конечный переадресатор (переопределение в самом производном классе, определяющее метод в иерархии конкретный экземпляр в использовании). Обратите внимание: если вы не используете ссылки/указатель, а локальные переменные, компилятор может разрешить вызов, и ему не нужно использовать механизм виртуальной отправки.

Ответ 5

Хорошо, я тестировал это для себя, потому что есть много вещей, о которых мы можем думать:

#include <iostream>
using namespace std;
class A
{
public:
    virtual void v() { cout << "A virtual" << endl; }
    void f() { cout << "A plain" << endl; }
};

class B : public A
{
public:
    virtual void v() { cout << "B virtual" << endl; }
    void f() { cout << "B plain" << endl; }
};

class C : public B
{
public:
    virtual void v() { cout << "C virtual" << endl; }
    void f() { cout << "C plain" << endl; }
};

int main()
{
    A * a = new C;
    a->f();
    a->v();

    ((B*)a)->f();
    ((B*)a)->v();
}

выход:

A plain
C virtual
B plain
C virtual

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

Виртуальные методы проверяют DATA экземпляра, на который указывает указатель, тогда как классические методы не вызывают метод, соответствующий указанному типу.

Точка этой функции следующая: предположим, что у вас есть массив A. Массив может содержать B, C, (или даже производные типы.). если вы хотите последовательно вызывать один и тот же метод для всех этих экземпляров, вы вызываете каждый из них, который вы перегружаете.

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

Что касается VFtables, то я никогда не объяснял, какой код он добавляет, и очевидно, что здесь, где С++ требует гораздо больше опыта, чем C, и это может быть основной причиной того, что С++ был помечен как "медленный" в своем ранние дни: на самом деле, это мощно, но точно так же, как и все, оно мощно, если вы знаете, как его использовать, иначе вы просто "удалите всю свою ногу".

Ответ 6

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

Ответ 7

Это очень важный аспект программирования на С++ - почти каждое интервью, в котором я участвовал, мне задают этот вопрос.

Что произойдет, если вы измените основной объект на:

int main() { Parent* a = new Child(); a->say(); return 0; }

Кроме того, стоит понять, что такое vtable.