- •Объектно-ориентированное программирование:
- •Содержание
- •Новый тип данных – класс
- •1.2. Доступность компонентов класса
- •1.3. Конструктор и деструктор
- •1.4.4. Статические члены класса
- •1.4. Компоненты-данные и компоненты-функции
- •1.4.1. Данные – члены класса
- •1.4.2. Функции – члены класса
- •1.4.3. Константные компоненты-функции
- •Указатели на компоненты класса
- •1.5.1. Указатели на компоненты-данные
- •1.5.2.Указатели на компоненты-функции
- •1.6. Указатель this
- •1.7. Друзья классов
- •7.1. Дружественная функция
- •1.7.2. Дружественный класс
- •1.8. Определение классов и методов классов
- •2. Наследование
- •2.1. Определение производного класса
- •2.2. Конструкторы и деструкторы производных классов
- •2.3. Виртуальные функции
- •2.4. Абстрактные классы
- •2.5. Включение объектов
- •2.6. Включение и наследование
- •2.7. Множественное наследование
- •2.8. Локальные и вложенные классы
- •2.9. Пример программы для Microsoft Visual Studio
- •2.10. Упражнения
- •3. Перегрузка операций
- •3.1. Перегрузка унарных операций
- •3.2. Перегрузка бинарных операций
- •3.4. Перегрузка операции вызова функции
- •3.5. Перегрузка операции присваивания
- •3.6. Перегрузка операции new
- •3.7. Перегрузка операции delete.
- •3.8. Основные правила перегрузки операций
- •3.9. Примеры программ
- •4. Шаблоны функций и классов
- •4.1. Шаблоны функций
- •4.2. Шаблоны классов
- •4.3. Компонентные функции параметризованных классов
- •4.4. Примеры программ.
- •5. Обработка исключительных ситуаций
- •5.2. Получение дополнительной информации об исключении
- •5.3. Определение типа исключения
- •5.4. Иерархия исключений
- •5.5. Спецификация функций, обрабатывающих исключения
- •6. Потоковые классы
- •6.1. Библиотека потоковых классов
- •6.3. Стандартные потоки ввода-вывода
- •6.4. Форматирование
- •6.5. Манипуляторы
- •6.6. Ввод-вывод объектов пользовательских классов
- •6.7. Определение пользовательских манипуляторов
- •6.8. Пользовательские манипуляторы с параметрами
- •6.9. Использование макросов для создания манипуляторов
- •6.10. Состояние потока
- •6.11. Неформатированный ввод-вывод
- •6.12. Файловый ввод-вывод
- •7.1. Пространство имен
- •7.2. Динамическая идентификация типов
- •7.3. Безопасное приведение типа
- •8. Стандартная библиотека шаблонов
- •8.1. Введение в stl
- •Парадигму обобщенного программирования можно сформулировать следующим образом:
- •8.2. Итераторы
- •8.3. Классы-контейнеры
- •Iterator find(const key_type&k)
- •Iterator lower_bound(const key_type&k)
- •Iterator upper_bound(const key_type&k)
- •8.4. Контейнер vector
- •8.5. Многомерные массивы
- •8.6. Ассоциативные контейнеры
- •8.7. Объекты-функции
- •8.8. Алгоритмы
- •I. Немодифицирующие операции
- •II. Модифицирующие операции
- •III. Сортировка
- •IV. Работа с множествами
- •V. Минимумы и максимумы
- •VI. Перестановки
2.3. Виртуальные функции
К механизму виртуальных функций обращаются в тех случаях, когда в каждом производном классе требуется свой вариант некоторой компонентной функции. Классы, включающие такие функции называются полиморфными и играют особую роль в ООП.
Рассмотрим, как ведут себя при наследовании невиртуальные компонентные функции с одинаковыми именами, типами и сигнатурами параметров.
Пример 2.3.1.
class base
{
public :
void print(){cout<<“\nbase”;}
};
class dir : public base
{
public:
void print(){cout<<“\ndir”;}
};
void main()
{
base B,*bp = &B;
dir D,*dp = &D;
base *p = &D;
bp –>print(); // base
dp –>print(); // dir
p –>print(); // base
}
В последнем случае вызывается функция print базового класса, хотя указатель p настроен на объект производного класса. Дело в том, что выбор нужной функции выполняется при компиляции программы и определяется типом указателя, а не его значением. Такой режим называется ранним или статическим связыванием.
Промер 2.3.2
//птицы
class bird{
//...
public:
void fly()const{cout<<"fly"<<endl;} //может летать
};
//пингвин
class penguin:public bird{
//...
public:
void fly()const{cout<<"nofly"<<endl;} //не летает
};
int main()
{bird b; penguin p;
b.fly(); //летит
p.fly(); //не летит
return 0;
}
Выполним эту программу. Все нормально: птицы летают, но пингвин не летает.
Усложним задание. Добавим функцию alarm() – сигнал «тревога», который поступает всем птицам, в том числе и пингвину. Реакция птиц на сигнал «тревога» : убежать, улететь.
//Сигнал тревоги
void alarm(const bird& b)
{
b.fly();
}
int main()
{
bird b;
penguin p;
b.fly(); //летит
p.fly(); //не летит
alarm(b); //полетела
alarm(p); //как ни странно, и пингвин полетел ???
return 0;
}
Выполним эту программу. Видим, что все птицы, в том
числе и пингвин, летят.
Почему так происходит?
void person::show()
{
cout<<name<<“ “<<age<<endl;
}
void student::show()
{
cout<<name<<“ “<<age<<“ “<<grade<<endl;
}
void teacher::show()
{
cout<<name<<“ “<<age<<“ “<<post<<endl;
}
void main()
{
person* p;
p=new person(“Иванов”,35);
p->show(); // Что будет выведено?
p=new student(“Петров”,21,75.8);
p->show(); // Что будет выведено?
p=new teacher(“Поляков”,51,”Декан”);
p->show(); // Что будет выведено?
return 0;
}
Ожидаем следующее
void main()
{
person* p;
p=new person(“Иванов”,35);
p->show(); // Иванов 25
p=new student(“Петров”,21,75.8);
p->show(); // Петров 21 75.5
p=new teacher(“Поляков”,51,”Декан”);
p->show(); // Поляков 51 Декан
return 0;
}
А на самом деле выведено
void main()
{
person* p;
p=new person(“Иванов”,35);
p->show(); // Иванов 25
p=new student(“Петров”,21,75.8);
p->show(); // Петров 21
p=new teacher(“Поляков”,51,”Декан”);
p->show(); // Поляков 51
return 0;
}
Почему так происходит?
Это происходит потому, что вызывается функция show() базового класса, хотя указатель p настроен на объект производного класса.
void alarm(const bird& b)
{ b.fly(); }
person* p;
p->show();
Дело в том, что выбор нужной функции выполняется при компиляции программы и определяется типом указателя, а не его значением.
Как мы уже знаем, такой режим называется ранним или статическим связыванием.
Большую гибкость обеспечивает позднее (отложенное) или динамическое связывание, которое предоставляется механизмом виртуальных функций. Любая нестатическая функция базового класса может быть сделана виртуальной, для чего используется ключевое слово virtual.
Пример 2.3.3
class base
{
public:
virtual void print(){cout<<“\nbase”;}
. . .
};
// и так далее – см. предыдущий пример.
В этом случае будет напечатано
base
dir
dir
Пример 2.3.4
class person
{
protected:
string name;
int age;
public:
person(string Name=“Noname”,int Age=0): name(Name),age(Age){}
virtual void show();
};
class student:public person
{
protected:
double grade;
public:
student(string Name=“Noname”, int Age=0, double Grade=0.0):
person(Name,Age),grade(Grade){}
virtual void show();
};
Таким образом, интерпретация каждого вызова виртуальной функции через указатель на базовый класс зависит от значения этого указателя, т.е. от типа объекта, для которого выполняется вызов.
Виртуальные функции–это функции, объявленные в базовом классе и переопределенные в производных классах. Иерархия классов, которая определена открытым наследованием, создает родственный набор пользовательских типов, на все объекты которых может указывать указатель базового класса. Выбор того, какую виртуальную функцию вызвать, будет зависеть от типа объекта, на который фактически (в момент выполнения программы) направлен указатель, а не от типа указателя.
Виртуальными могут быть только нестатические функции-члены.
Виртуальность наследуется. После того, как функция определена как виртуальная, ее повторное определение в производном классе (с тем же самым прототипом) создает в этом классе новую виртуальную функцию, причем спецификатор virtual может не использоваться.
Конструкторы не могут быть виртуальными, в отличие от деструкторов. Практически каждый класс, имеющий виртуальную функцию, должен иметь виртуальный деструктор.
Если в производном классе ввести функцию с тем же именем и типом, но с другой сигнатурой параметров, то эта функция производного класса не будет виртуальной.
Виртуальная функция может быть дружественной в другом классе.
Механизм виртуального вызова может быть подавлен с помощью явного использования полного квалифицированного имени.
В Visual Studio C++ .NET для переопределения виртуальной функции вы можете использовать ключевое слово override
class person
{
public:
virtual void show();
};
class student:public person
{
public:
void show()override;
};
