Наследование и полиморфизм

Содержание

Слайд 2

Наследование и полиморфизм Наследование Наследованием называется процесс порождения одного класса от

Наследование и полиморфизм

Наследование
Наследованием называется процесс порождения одного класса от другого класса

с сохранением и/или перекрытием свойств и методов класса-предка и добавлением, при необходимости, новых свойств и методов в классы-потомки.
Набор классов, связанных отношением наследования, называют иерархией классов или схемой наследования.
Пример наследования. Фигуры на экране.
Рассмотрим следующую схему наследования:
Общим свойством всех фигур на экране, независимо от их формы, является наличие координат (класс TLocation).
Любая фигура на экране должна “уметь” себя отобразить, скрыть своё изображение, а также перемещаться по экрану (класс TFigure).
Слайд 3

Наследование и полиморфизм enum bool { false, true }; class TLocation

Наследование и полиморфизм

enum bool { false, true };
class TLocation {
protected:

int X, Y; // координаты фигуры на экране
public:
TLocation ( int a, int b ) { X = a; Y = b; } // конструктор
int GetX ( ) { return X; } // получение координаты X
int GetY ( ) { return Y; } // получение координаты Y
};

сlass TFigure : public TLocation { // абстрактная фигура
protected:
bool Vision; // признак видимости фигуры на экране
public:
TFigure ( int a, int b ) : TLocation ( a, b ) { } // конструктор
void Show ( ) { } // метод будет перекрыт у потомков класса
void Hide ( ) { } // метод будет перекрыт у потомков класса
bool IsVisible ( ) { return Vision; } // получение признака видимости
void MoveTo ( int newX, int newY ) { // перемещение фигуры по экрану
bool V = IsVisible ( ); // признак видимости фигуры
if ( V ) Hide ( ); // если фигура видима – скроем её
X = newX; Y = newY; // изменим координаты фигуры
if ( V ) Show ( ); } // если фигура была видима –
}; // покажем её на новом месте

Слайд 4

Наследование и полиморфизм Обратите внимание на то, что метод MoveTo, разработанный

Наследование и полиморфизм
Обратите внимание на то, что метод MoveTo, разработанный для

абстрактной фигуры TFigure, теперь будет работать для любой конкретной фигуры.

сlass TCircle : public TFigure { // конкретная фигура - окружность
int R; // радиус окружности
public:
TCircle ( int a, int b, int r ) : TFigure ( a, b ) { R = r; } // конструктор
void Show ( ) { // перекрываем метод Show
// используем графические примитивы из GDI+ для Win32
// hdc - дескриптор контекста графического устройства
// hdc всегда известен для окна, в котором будем рисовать
Ellipse( hdc, X-R, Y-R, X+R, Y+R ); // рисуем окружность
Vision = true; } // фигура видна
void Hide ( ) { // перекрываем метод Hide
// сохраним цвет текущего пера (считаем, что фон полотна - белый)
HGDIOBJ pen = SelectObject(hdc,GetStockObject(DC_PEN));
SetDCPenColor(hdc, RGB(0,0,0) ); // установим белый цвет пера
Ellipse( hdc, X-R, Y-R, X+R, Y+R ); // отобразим круг цветом фона
SelectObject(hdc, pen ); // восстановим цвет текущего пера
Vision = false; } // фигура не видна
};

Слайд 5

Наследование и полиморфизм Множественное наследование Множественное наследование означает, что у классов-потомков

Наследование и полиморфизм

Множественное наследование
Множественное наследование означает, что у классов-потомков может быть

более, чем один непосредственный класс-предок. В С++ такое допускается. Рассмотрим некоторый визуальный объект, который представляет собой текстовую строку, заключенную в окружность. Построим схему наследования:

текст

Слайд 6

Наследование и полиморфизм Поскольку классы-потомки наследуют все данные и методы классов-предков,

Наследование и полиморфизм

Поскольку классы-потомки наследуют все данные и методы классов-предков, в

итоге имеем следующую картину:

В результате такой схемы наследования, все данные и методы класса TLocation будут представлены в итоговом классе Text_in_Circle в двух экземплярах.
Возникает неоднозначность: каким экземпляром данных пользоваться?
Данная неприятная ситуация преодолевается в С++ путем применения виртуальной схемы наследования (о ней позже).

Слайд 7

Наследование и полиморфизм Виртуальные функции Переопределим ранее введенные классы TFigure и

Наследование и полиморфизм

Виртуальные функции
Переопределим ранее введенные классы TFigure и TCircle следующим

образом:

сlass TFigure : public TLocation { // абстрактная фигура
protected:
bool Vision; // признак видимости фигуры на экране
public:
TFigure ( int, int ) ; // конструктор
virtual ~TFigure ( ) ; // виртуальный деструктор
virtual void Show ( ) ; // виртуальная функция
virtual void Hide ( ) ; // виртуальная функция
bool IsVisible ( ) ; // получение признака видимости
void MoveTo ( int, int ) ; // перемещение фигуры по экрану
};

сlass TCircle : public TFigure { // конкретная фигура - окружность
int R; // радиус окружности
public:
TCircle ( int a, int b, int r ) : TFigure ( a, b ) { R = r; } // конструктор
virtual ~TCircle ( ) ; // виртуальный деструктор
virtual void Show ( ) { … } // перекрываем виртуальную функцию Show
virtual void Hide ( ) { … } // перекрываем виртуальную функцию Hide
};

Слайд 8

Наследование и полиморфизм В чём суть использования виртуальных функций? Важно это

Наследование и полиморфизм

В чём суть использования виртуальных функций? Важно это записать

и понять.
Если в сигнатуре функции-члена класса слово «virtual» не использовалось, тогда имя функции и исполняемый код функции связываются друг с другом на этапе компиляции. Связывание выполняет компилятор. Данный механизм называется ранним связыванием. Методы потомков перекрывают одноименные методы предков на этапе компиляции кода.
Если в сигнатуре функции-члена класса использовалось слово «virtual», тогда имя функции и её исполняемый код связываются друг с другом непосредственно на этапе исполнения кода. Связывание выполняет конструктор объекта. Данный механизм называется поздним связыванием. Методы потомков перекрывают одноименные методы предков только на этапе исполнения кода.
Когда работает механизм позднего связывания, компилятор отыскивает в исходном коде все виртуальные функции и формирует таблицу виртуальных функций, которую упрощенно можно представлять себе так:

Адреса исполняемого кода в таблицу заносят конструкторы объектов по мере того, как эти объекты создаются. Это и есть полиморфизм. Обеспечивается динамичность и гибкость кода, без его повторной компиляции.

Слайд 9

Наследование и полиморфизм Абстрактные классы Абстрактные классы используются для порождения производных

Наследование и полиморфизм

Абстрактные классы
Абстрактные классы используются для порождения производных классов.
«Нет

ничего практичнее хорошей абстракции» (перефразируя Р.Кирхгофа).
Переопределим абстрактную фигуру ещё раз следующим образом:
Класс называется абстрактным, если он содержит хотя бы одну чистую виртуальную функцию. Создание объектов абстрактных классов бессмысленно, т.к. такие объекты будут нежизнеспособны. Чистые виртуальные функции должны быть перекрыты в классах-потомках в обязательном порядке.
Таким образом, абстрактные классы как бы «вынуждают» своих потомков реализовывать определенные минимальные модели поведения, обеспечивая надлежащую функциональность производных классов.

сlass TFigure : public TLocation { // абстрактная фигура
protected:
bool Vision; // признак видимости фигуры на экране
public:
TFigure ( int, int ) ; // конструктор
virtual ~TFigure ( ) ; // виртуальный деструктор
virtual void Show ( ) = 0 ; // чистая виртуальная функция
virtual void Hide ( ) = 0 ; // чистая виртуальная функция
bool IsVisible ( ) ; // получение признака видимости
void MoveTo ( int, int ) ; }; // перемещение фигуры по экрану

Слайд 10

Наследование и полиморфизм Виртуальная схема наследования Виртуальная схема наследования позволяет избегать

Наследование и полиморфизм

Виртуальная схема наследования
Виртуальная схема наследования позволяет избегать неоднозначности в

процессе множественного наследования.
Это достигается за счёт применения механизма позднего связывания и использования ссылочных типов.
При этом, вся внутренняя работа выполняется компилятором и конструкторами объектов. Разработчику кода необходимо лишь указать ключевое слово «virtual» при описании процесса наследования.
Пример. Пусть есть такие описания:

// Вариант 1 - используем обычную схему множественного наследования
class Base { ….. };
class Derived : public Base, public Base { ….. }; // так нельзя (синтаксис!)
class X : public Base { ….. };
class Y : public Base { ….. };
class Derived : X, Y { ….. }; // так можно, в Derived попадёт две копии Base
// Вариант 2 - используем виртуальную схему множественного наследования
class X : virtual public Base { ….. };
class Y : virtual public Base { ….. };
class Derived : X, Y { ….. }; // так можно, причём в Derived
// избежим неоднозначности с Base

Слайд 11

Наследование и полиморфизм Вариант 1 – обычная схема множественного наследования Вариант

Наследование и полиморфизм

Вариант 1 – обычная схема
множественного наследования

Вариант 2 – виртуальная

схема
множественного наследования
Слайд 12

Наследование и полиморфизм Ссылки на объекты классов Предположим, в нашем распоряжении

Наследование и полиморфизм

Ссылки на объекты классов
Предположим, в нашем распоряжении имеется некая

функция
int GetDelta ( int&, int& );
которая отслеживает движения мыши в каждый малый интервал времени по координатам X и Y (соответственно первый и второй аргументы функции). Функция возвращает «истину», если движение мыши имело место, и «ложь», если мышь оставалась неподвижной. Рассмотрим следующий фрагмент кода:
Обратите внимание: функция Drag будет вслед за мышью перемещать по экрану любую фигуру, ничего не зная о форме этой конкретной фигуры. Важно лишь, чтобы эта фигура была потомком абстрактной фигуры TFigure.
Такое поведение возможно, потому что все фигуры-потомки TFigure способны выдавать свои координаты, а также себя изображать, стирать или передвигать.

void Drag ( TFigure &Any ) {
int X, dX, Y, dY; // координаты и их изменение
Any.Show(); // отрисовать фигуру
X = Any.GetX(); // получить текущую координату X
Y = Any.GetY(); // получить текущую координату Y
while ( GetDelta ( dX, dY ) ) // пока движется мышь
{ X =+ dX; Y += dY; Any.MoveTo ( X, Y ); } // движется и фигура
}

Слайд 13

Наследование и полиморфизм Конструкторы Конструктором называется функция-член класса, которая всегда вызывается

Наследование и полиморфизм

Конструкторы
Конструктором называется функция-член класса, которая всегда вызывается при создании

объектов класса. Назначение конструктора – привести объект в «рабочее» состояние. Например: получить необходимые ресурсы, открыть файлы, инициализировать данные, осуществить позднее связывание.
Имя конструктора должно совпадать с именем класса. Конструкторы не имеют возвращаемых значений. Конструкторы могут быть перегружены, как любая другая функция. Конструкторы не могут быть виртуальными функциями.
Существует три вида конструкторов: «нуль-конструктор (или конструктор по умолчанию)», «конструктор копирования» и конструктор общего вида.
Нуль-конструктор аргументов не имеет. Если в описании класса не было указано ни одного конструктора, нуль-конструктор создаётся автоматически.
Конструктор копирования имеет ровно один аргумент, который должен быть ссылкой на объект того же класса, что и создаваемый объект.
Все прочие конструкторы относятся к конструкторам общего вида.
Конструктор объекта явно или неявно вызывается всякий раз, когда объект создается, независимо от класса памяти объекта.
Конструкторы глобальных (внешних) объектов вызываются до того, как получит управление функция main(). Конструкторы объектов в динамической памяти вызываются операцией new. Конструкторы локальных объектов вызываются, когда становится активной область действия соответствующего объекта.
Для объектов классов, которые связаны отношениями наследования, порядок вызова конструкторов регулируется специальными правилами (см. далее).
Слайд 14

Наследование и полиморфизм Пример 1. Три вида конструкторов Выбор конкретного кода

Наследование и полиморфизм

Пример 1. Три вида конструкторов
Выбор конкретного кода конструктора при

создании объекта класса производится аналогично тому, как выбирается конкретный код при вызове перегруженных функций.
Основной принцип – сигнатура конструктора (т.е. количество и типы аргументов конструктора) должна соответствовать количеству и типам аргументов при вызове конструктора.
Если в классе нет ни одного конструктора, тогда автоматически создаётся и вызывается 0-конструктор («конструктор по умолчанию»).

сlass X { // виды конструкторов
…………….
public:
X ( ) ; // 0-конструктор (конструктор по умолчанию)
X ( const X& ) ; // конструктор копирования
X ( int n = 0 ) ; // конструктор общего вида
};
void main ( void ) {
X one; // вызывается 0-конструктор
X two ( 1 ); // вызывается конструктор общего вида
X three = 1; // вызывается конструктор общего вида
X four = one; // вызывается конструктор копирования
X five ( two ); } // вызывается конструктор копирования

Слайд 15

Наследование и полиморфизм Пример 2. Вызов конструкторов при наследовании Правила вызова

Наследование и полиморфизм

Пример 2. Вызов конструкторов при наследовании
Правила вызова конструкторов следующие:
Сначала

вызываются конструкторы базовых классов, затем вызываются конструкторы производных классов (сначала создаются объекты-предки, затем объекты-потомки).
Конструкторы виртуальных классов (виртуальная схема наследования) запускаются ранее конструкторов прочих классов.
Для вложенных объектов и производных классов могут использоваться списки инициализации.

сlass Derived : Base1, virtual Base2 { ….. }; // два базовых класса
Derived X; // порядок вызова: Base2(); Base1(); Derived()
class Table { ….. } // некоторый класс
class A {
Table B; // вложенный объект
Table C; // вложенный объект
int n; // элемент данных
…..
public:
A ( int size ) ; // конструктор
~A ( ) ; } ; // деструктор
A :: A ( int size ) : B ( size ), C ( size ) { n = size; } // список инициализации