
- •Конспект лекций по курсу «Объектно-ориентированное программирование»
- •1. Введение
- •Эффективные типы, определяемые пользователем
- •Копирование объектов класса
- •Перегрузка операторов
- •Дружественные функции
- •Наследование
- •Виртуальные функции
- •Абстрактные классы
- •Включение
- •Шаблоны
- •Стандартная библиотека
Виртуальные функции
Виртуальные функции решают проблему, связанную с полем типа. Для того, чтобы сделать функцию виртуальной, нужно поставить ключевое слово virtual слева от объявления функции в классе. Например:
class employee_v{
public:
virtual void print()const;
};
При этом определение виртуальной функции синтаксически такое же, как и у обычной невиртуальной функции-члена:
void employee_v::print()const{
cout<<"employee_v::print"<<endl;
}
Виртуальная функция замещается в производном классе:
class programmer_v:public employee_v{
public:
virtual void print()const;
};
void programmer_v::print()const{
cout<<"programmer_v::print"<<endl;
}
Вызов виртуальной функции синтаксически такой же, как и обычной функции:
employee_v r1;
programmer_v p1;
r1.print();//employee_v::print
p1.print();//programmer_v::print
В этих двух случаях вызовутся функцииprintсоответствующих классов. Так было бы и в случае невиртуальных функцийprint(т.е. без ключевого словаvirtual).
void print_v(employee_v* pr){
pr->print();//employee_v::printилиprogrammer_v::print
}
В этом случае мы вызываем функцию-член (print) по указателюprнаemployee_v. В случае, если бы функция-членprintбыла объявлена невиртуальной, то однозначно вызвалась бы функцияemployee_v::print(что определяется типом указателя). Однако, так как функцияprintвиртуальна, вызывается либоemployee_v::print, либоprogrammer_v::print, в зависимости от того, указывает prна объект типаemployee_vлибо на объект типаprogrammer_v. Например:
employee_v r1,r2;
programmer_v p1,p2;
print_v(&r1);// employee_v::print
print_v(&p1);// programmer_v::print
Теперь мы можем запомнить указатели на различные объекты в массиве и вызвать print_vв цикле:
employee_v *record[4]={&r1,&p1,&r2,&p2};
for(int i=0;i<4;++i){
print_v(record[i]);
}
Вызовутся функции в таком порядке: employee_v::print, programmer_v::print, employee_v::print, programmer_v::print.
Так как наша функция print_vвыродилась в единственный вызов функции-членаprint, мы можем написать непосредственно (что эквивалентно предыдущему примеру):
for(int i=0;i<4;++i){
record[i]->print();
}
В случае, если мы используем виртуальные функции, добавление нового производного от employeeкласса не требует изменений ни в базовом классе, ни в коде, использующем виртуальные функции (в нашем случае этот код – приведенный выше цикл). Заметьте, что это будет работать, даже если указанный цикл был написан и откомпилирован до того, как производный классprogrammer_v::print был вообще задуман! Данный факт служит краеугольным камнем объектно-ориентированных проектов и придает стабильность развивающейся программе.
Полиморфизм
Когда функции базового класса (employee_v) ведут себя «правильно» независимо от того, какой конкретно производный класс используется, это называетсяполиморфизмом. Тип, имеющий виртуальные функции, называетсяполиморфным типом. Для достижения полиморфного поведения вC++ вызываемые функции-члены должны быть виртуальными, и доступ к объекту должен осуществляться через ссылки или указатели. При непосредственных манипуляциях с объектом (без помощи указателя или ссылки) его точный тип известен компилятору, и поэтому полиморфизм времени выполнения не требуется.