- •Наследование классов, виртуальные функции
- •Теоретические сведения
- •Передача параметров в базовый класс
- •Типы наследования (уровни доступа к базовому классу)
- •Public – наследование
- •Использование методов
- •Private – наследование
- •Использование методов
- •Protected – наследование
- •Виртуальные функции и полиморфизм
- •Контрольные вопросы
- •Общие требования выполнения работы
Виртуальные функции и полиморфизм
Механизм виртуальных функций в ООП используется для создания метода, предназначенного для работы с различными (по типам) объектами. Виртуальные функции объявляются в базовом и производных классах с ключевым словом virtual. Механизм позднего связывания действует автоматически, программисту достаточно объявить функцию в базовом классе как virtual и переопределить ее в производных классах.
Но неплохо понимать этот автоматический механизм: каждый объект класса, управляемого из базового класса с виртуальными функциями, имеет указатель на таблицу с именем vmtbl (virtual method table), содержащую адреса виртуальных функций. Эти 35 адреса устанавливаются в адреса нужных для данного объекта функций во время выполнения.
В отличие от перегружаемых функций виртуальные объявляются в производных классах с тем же именем, возвращаемым значением и типом аргументов. Если различны типы аргументов, виртуальный механизм игнорируется. Тип возвращаемого значения переопределить также нельзя.
Итак, повторим: основная идея в использовании виртуальной функции состоит в следующем - она может быть объявлена в базовом классе, а затем переопределена в каждом производном классе. При этом доступ через указатель на объект базового класса осуществляется к этой функции из базового класса, а доступ через указатель на объект производного класса – из производного класса. То же происходит при передаче функции объекта производного класса, если аргумент объявлен как базовый класс.
Объявим в базовом классе виртуальную функцию show ().
class point
{. . .
virtual void show ()
{ cout<<x<<'\t'<<y<<endl;}
};
В производных классах circ и сylinder определение функций остается без изменений. Обращаем внимание на то, что указатель базового класса может адресовать объекты как базового, так и производного класса.
int _tmain(int argc, _TCHAR* argv[])
{point p1(1,2),*pp;
cout<<"-------p1-----"<<endl;
pp=&p1; // Указатель базового класса связываем с точкой
pp->show(); // печать информации о точке
circ s1(1,2,3);
cout<<"-------s1-----"<<endl;
pp=&s1; // Указатель базового класса связываем с окружностью
pp->show(); // печать информации об окружности
Cylinder c2(0,10,15,50);
pp=&c1; // Указатель базового класса связываем с цилиндром
pp->show(); // печать информации о цилиндре
system("pause");
return 0;
}
Как легко заметить, в отличие от предыдущего примера ,где объект класса вызывал функцию( вызов p1.show() и т.п.), в данном примере по внешнему виду невозможно определить функция какого класса будет вызываться (вызов pp->show() не несет этой информации), поэтому как всегда при динамической работе, все определяет контекст работы программы, и этот контекст может динамически изменяться.
Ограничения на использование виртуальных функций:
Переопределяемая функция в производных классах должна соответствовать прототипу виртуальной функции базового класса. То есть при переопределении виртуальной функции интерфейс функции должен в точности соответствовать прототипу. Если же такого соответствия нет, то такая функция просто рассматривается как перегруженная и она утрачивает свои виртуальные свойства.
Виртуальная функция должна быть членом, а не другом класса, для которого она определена. Тем не менее виртуальная функция может быть другом другого класса.
Хотя деструктор может быть виртуальным, но конструктор виртуальным быть не может.
Если в производном классе виртуальная функция не переопределяется, то тогда используется ее версия из базового класса. Например, если закомментировать функцию show() в классе Cylinder, то увидим: circle : x=2 y=3 r=6 p=37.68 q=113.04
int main(int argc, char* argv[])
{ . . .
Cylinder c1,c2(0,10,15,50),c3(s2,9);
pp= &c3; //указатель связывается с объектом класса Cylinder
pp->show(); // вызывается show() класса circ
}
В общем случае, когда класс не переопределяет виртуальную функцию, С++ использует первое из определений, которое он находит, идя от потомков к предкам.
