Что цель использовать зарезервированное слово, виртуальное перед функциями? Если я хочу, чтобы дочерний класс переопределил родительскую функцию, я просто объявляю ту же функцию такой как 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()
?
Огромное спасибо.
Я думаю, это классический вопрос о том, как работает полиморфизм. Основная идея заключается в том, что вы хотите абстрагироваться от конкретного типа для каждого объекта. Другими словами: вы хотите иметь возможность вызывать экземпляры 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();
Как вы понимаете, это очень мощно. Это позволяет вам расширять родительский объект столько раз, сколько вы хотите, и функции, которые принимают родительский указатель, по-прежнему будут работать. Чтобы это работало, как упоминают другие, вам необходимо объявить метод как виртуальный.
Когда вы используете ключевое слово virtual, создается таблица виртуальных функций для поиска правильных методов в экземпляре. Тогда, даже если на производный экземпляр указывает указатель базового класса, он все равно найдет правильную реализацию метода.
Если вы не используете ключевое слово virtual
, вы не переопределяете, а скорее определяете несвязанный метод в производном классе, который будет скрывать метод базового класса. То есть, без virtual
, Base::say
и Derived::say
не связаны - кроме совпадения имен.
Когда вы используете ключевое слово virtual (обязательное в базовом, необязательное в производном классе), вы сообщаете компилятору, что классы, производные от этой базы, смогут переопределить этот метод. В этом случае Base::say
и Derived::say
считаются переопределением одного и того же метода.
Когда вы используете ссылку или указатель на базовый класс для вызова виртуального метода, компилятор добавит соответствующий код, чтобы был вызван конечный переопределитель (переопределение в самом производном классе, который определяет метод в иерархии используемого конкретного экземпляра). Обратите внимание, что если вы используете не ссылки/указатели, а локальные переменные, компилятор может разрешить вызов и ему не нужно использовать механизм виртуальной диспетчеризации.
Если бы функция была виртуальной, то вы могли бы сделать это и получить на выходе "2":
Parent* a = new Child();
a->say();
Это работает, потому что виртуальная
функция использует фактический тип, тогда как невиртуальная функция использует объявленный тип. Почитайте о полиморфизме, чтобы лучше понять, зачем это нужно.
Попробуйте с:
Parent *a = new Child();
Parent *b = new Parent();
a->say();
b->say();
Без virtual
, оба с print '1'. Добавьте virtual, и дочерний элемент будет вести себя как Child, даже если на него ссылаются через указатель на Parent
.
Это очень важный аспект программирования на c++ - почти на каждом собеседовании, на котором я был, мне задавали этот вопрос.
Что произойдет, если вы измените main на:
int main() { Parent* a = new Child(); a->say(); return 0; }
Также стоит понять, что такое vtable.