- •Наследование. Абстрактные классы. Виртуальные функции Контейнерные классы
- •Наследование
- •Простое наследование
- •Спецификаторы доступа базовых классов
- •Порядок вызова конструкторов
- •Порядок вызова деструкторов
- •Разрешение области видимости
- •Теперь вызов функции b.Fin1() приведет к вызову функции fun() класса a. Динамическое связывание в ооп (полиморфизм)
- •Наследование и таблица виртуальных функций
- •Абстрактные базовые классы
- •Виртуальные деструкторы
- •Механизм работы позднего связывания
- •Чистые виртуальные деструкторы
- •Виртуальные функции в деструкторах
- •Перегруженные функции при динамическом связывании
- •Множественное наследование
- •Что необходимо знать
- •Задания
Порядок вызова конструкторов
При создании экземпляра класса вызывается его конструктор. Если класс является производным, то должен быть вызван конструктор базового класса. Порядок вызова в С++ фиксирован. Если базовый класс, в свою очередь, является производным, то процесс рекурсивно повторяется до тех пор, пока не будет достигнут корневой класс.
Например,
class First {…};
class Second: public First {…};
class Third: public Second {…};
При создании экземпляра класса Third конструкторы вызываются в следующем порядке:
First::First()
Second::Second()
Third::Third()
Порядок вызова деструкторов
Деструкторы для производных классов вызываются в порядке обратном вызову конструкторов. Таким образом, порядок вызовов деструкторов, сгенерированных для разрушения экземпляра класса Third, будет следующим:
Third::~Third()
Second::~Second()
First::~First()
Разрешение области видимости
Порождая один класс от другого, можно прийти к такой ситуации, когда в нескольких классах используются переменные и функции с одинаковыми именами.
Например:
class A
{public:
int fun() {return 1;}
};
class B : public A
{public:
int fun() {return 2;}
};
void main()
{ A a;
B b;
int i = a.fun(); //i=1
int j=b.fun(); //j=2
}
В этом случае компилятор действует по следующему алгоритму: если имя в базовом классе переобъявляется в производном, то имя в производном классе подавляет соответствующее имя в базовом.
В С++ можно заставить компилятор «видеть» за пределами текущей области видимости. Для этого используются оператор разрешения видимости. Общая форма этого оператора такова:
<имя класса>::<идентификатор из класса>,
где <имя класса> - это имя базового или производного класса, а <идентификатор из класса> - это имя любой переменной или функции, объявленной в классе.
Модифицируем наш класс B следующим образом:
class B : public A
{public:
int fun() {return 2;}
int fun1() {return A::fun();}
};
Теперь вызов функции b.Fin1() приведет к вызову функции fun() класса a. Динамическое связывание в ооп (полиморфизм)
Полиморфизм – это свойство ООП, при котором одно и тоже сообщение может вызывать различные действия на этапе выполнения.
Полиморфизм – это характеристика функций-членов, а не объектов. Несмотря на то, что полиморфизм реализуется через архитектуру класса, полиморфными могут быть только функции-члены класса, а не весь класс. В С++ полиморфная функция привязывается к одной из возможных функций только тогда, когда ей передается конкретный объект, т. е. в С++ вызов функции в исходном коде только обозначается, без точного указания на то, какая именно функция вызывается. Этот процесс называется позднее связывание. Процесс, при котором компилятор (как в традиционных языках), базируясь на исходном коде, вызывает фиксированные идентификаторы функций, а компоновщик заменяет эти идентификаторы физическими адресами, называется раним связыванием. То есть идентификаторы функций ассоциируются с физическими адресами до этапа выполнения, еще на стадии компиляции и компоновки. При раннем связывании программы выполняются быстрее, но существенно ограничены возможности разработчика. При позднем связывании остро встает вопрос об эффективности исполняемой программы.
Способность объектно-ориентированных языков автоматически определять тип объекта на этапе выполнения программы называется RTTI (run-time type identification – идентификация во время выполнения).
Виртуальные функции
В С++ позднее связывание для функции определяется при ее объявлении с помощью ключевого слова virtual. Позднее связывание имеет смысл только для объектов, являющихся частью иерархии классов. Объявление функции виртуальной для класса (не используемого в качестве базового) синтаксически корректно, но приведет только к потере времени в момент выполнения.
Виртуальные функции – функции, вызов которых зависит от типа объектов. С помощью виртуальных функций объект определяет свои действия.
Правило: указатель на базовый класс может ссылаться на объект этого класса или любого другого, производного от базового.
A* aobject; A
B* bobject; B
C* cobject; C
cobject=&аobject // так нельзя делать
aobject=&cobject.
Пример:
//f1.h
#include <iostream.h>
class Animal
{public:
/*virtual*/ char* speak() {return "";}
};
class Dog: public Animal
{public:
char * speak() {return "Gav!!!";}
};
//f1.cpp
#include "f1.h"
void sound(Animal& i)
{cout<<i.speak()<<endl;}
void main()
{ Dog Sharic;
sound(Sharic); //”” (на экран будет выведена пустая строка)
}
Решение проблемы – позднее связывание. Функцию speak() класса Animal достаточно объявить виртуальной, после чего компилятор запустит механизмы позднего связывания.
//f1.h
#include <iostream.h>
#include <string.h>
class Animal
{
protected:
char *pname;
public:
Animal(char *AnName)
{
pname=new char[strlen(AnName)+1];
strcpy(pname, AnName);
}
virtual char* speak() {return "";}
virtual char *name() {return pname;}
};
class Dog: public Animal
{
public:
Dog(char *name):Animal(name) {}
char *speak()
{
char *phrase;
phrase=strdup(pname); //дублирует строку, при этом вызывая функцию malloc()
return strcat(phrase," Say Gav! ");
}
virtual char *sit()
{
char *phrase; phrase=strdup(pname); return strcat(phrase," sits");
}
};
//f1.cpp
#include "f1.h"
void main()
{
Animal* p[2]={new Animal("a"), new Dog("Sharic")};
cout<<p[0]->speak()<<endl; //выведет на экран пустую строку
cout<<p[1]->speak()<<endl; //выведет на экран строку “Sharic Say Gav!”
//cout<<p[1]->sit()<<endl; //ошибка компиляции: 'sit' : is not a member of 'Animal'
cout<<((Dog*)p[1])->sit()<<endl;
}
