Переопределение виртуальных
Какова цель использования зарезервированного слова 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()
?
Спасибо, куча.
Ответы
Ответ 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.