Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Подбельский учебник с++ / Подбельский - главы 10-12

.pdf
Скачиваний:
28
Добавлен:
22.05.2015
Размер:
1.46 Mб
Скачать

Глава 10. НАСЛЕДОВАНИЕ И ДРУГИЕ ВОЗМОЖНОСТИ КЛАССОВ

10.1. Наследование классов

Начиная рассматривать вопросы наследования, нужно отметить, что обоснованно введенный в программу объект призван моделировать свойства и поведение некоторого фрагмента решаемой задачи, связывая в единое целое данные и методы, относящиеся к этому фрагменту. В терминах объектно-ориентированной методологии объекты взаимодействуют между собой и с другими частями программы с помощью сообщений. В каждом сообщении объекту передается некоторая информация. В ответ на сообщение объект выполняет некоторое действие, предусмотренное набором компонентных функций того класса, которому он принадлежит. Таким действием может быть изменение внутреннего состояния (изменение данных) объекта либо передача сообщения другому объекту.

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

Объекты разных классов и сами классы могут находиться в отношении наследования, при котором формируется иерархия объектов, соответствующая заранее предусмотренной иерархии классов.

Иерархия классов позволяет определять новые классы на основе уже имеющихся. Имеющиеся классы обычно называют базовыми (иногда порождающими), а новые классы, формируемые на основе базовых,-производными(порожденными),иногдаклассами-потомка- ми или наследниками. Производные классы "получают наследство" - данные и методы своих базовых классов - и, кроме того, могут пополняться собственными компонентами (данными и собственными

Глава 10. Наследование и другие возможности классов

337

методами). Наследуемые компоненты не перемещаются в производный класс, а остаются в базовых классах. Сообщение, обработку которого не могут выполнить методы производного класса, автоматически передается в базовый класс. Если для обработки сообщения нужны данные, отсутствующие в производном классе, то их пытаются отыскать автоматически и незаметно для программиста в базовом классе (рис. 10.1).

 

Класс А

 

(базовый)

 

2

 

У

 

Класс В

 

(производный)

Входное сообщение

Выходное сообщение

 

••

Рис. 10.1. Схема обработки сообщений в иерархии объектов: 1 - обработка сообщения методами производного класса; 2 - обработка сообщения методами базового класса.

Если класс "точка (позиция) на экране" считать базовым классом, то на его основе можно построить класс "окно на экране". Данными этого класса будут две точки:

точка, определяющая левый верхний угол;

точка, определяющая размеры окна, т.е. смещения вдоль координатных осей относительно левого верхнего угла.

Методы класса "окно на экране":

сместить окно вдоль оси х на DX;

сместить окно вдоль оси Y на DY;

. сообщить значение координаты х левого верхнего угла;

сообщить значение координаты у левого верхнего угла;

сообщить размер окна вдоль оси х;

сообщить размер окна вдоль оси Y.

Конструктор окна на экране:

22-

338

Язык Си++

• создать окно на экране с заданным именем по двум точкам, определяющим левый верхний угол окна и его размеры.

Деструктор окна на экране:

• уничтожить окно с заданным именем.

Обратите внимание, что две точки по-разному используются в классе "окно на экране". Первая из них - это абсолютные координаты точки на экране, вторая - интерпретируется просто как пара чисел, определяющая размеры окна. Таким образом, если первая точка имеет координаты (4,3), а вторая (0,0), то это соответствует пустому окну (окну с нулевыми размерами). Наименьшее окно, в которое можно вывести один символ (или один пиксель в графическом режиме), должно иметь размеры (1,1) независимо от положения левого верхнего угла.

При наследовании некоторые имена методов (компонентных функций) и (или) компонентных данных базового класса могут быть по-новому определены в производном классе. В этом случае соответствующие компоненты базового класса становятся недоступными из производного класса. Для доступа из производного класса к компонентам базового класса, имена которых повторно определены в производном, используется операция • : •.' указания (уточнения) области видимости.

Любой производный класс может, в свою очередь, становиться базовым для других классов, и таким образом формируется направленный граф иерархии классов и объектов. В иерархии производный объект наследует разрешенные для наследования компоненты всех базовых объектов. Другими словами, у объекта имеется возможность доступа к данным и методам всех своих базовых классов.

Наследование в иерархии классов может отображаться и в виде дерева, и в виде более общего направленного ациклического графа. Допускается множественное наследование - возможность для некоторого класса наследовать компоненты нескольких никак не связанных между собой базовых классов. Например, класс "окно на экране" и класс "сообщение" совместно могут формировать новый класс объектов "сообщение в окне".

При наследовании классов важную роль играет статус доступа (статус внешней видимости) компонентов. Для любого класса все его компоненты лежат в области его действия. Тем самым любая принадлежащая классу функция может использовать любые компонентные данные и вызывать любые принадлежащие классу функции. Вне клас-

Глава 10. Наследование и другие возможности классов

339

са в общем случае доступны только те его компоненты, которые имеют статус public.

В иерархии классов соглашение относительно доступности компонентов класса следующее.

Собственные (private) методы и данные доступны только внутри того класса, где они определены.

Защищенные (protected) компоненты доступны внутри класса, в котором они определены, и дополнительно доступны во всех производных классах.

Общедоступные (public) компоненты класса видимы из любой точки программы, т.е. являются глобальными.

Если считать, что объекты, т.е. конкретные представители классов, обмениваются сообщениями и обрабатывают их, используя методы и данные классов, то при обработке сообщения используются, во-первых, общедоступные члены всех классов программы; вовторых, защищенные компоненты базовых и рассматриваемого классов и, наконец, собственные компоненты рассматриваемого класса. Собственные компоненты базовых и производных классов, а также защищенные компоненты производных классов недоступны для сообщения и не могут участвовать в его обработке.

Еще раз отметим, что на доступность компонентов класса влияет не только явное использование спецификаторов доступа (служебных слов) - private (собственный), protected (защищенный), public (общедоступный), но и выбор ключевого слова class, struct, union, с помощью которого объявлен класс.

Определение производного класса. В определении и описании производного класса приводится список базовых классов, из которых он непосредственно наследует данные и методы. Между именем вводимого (нового) класса и списком базовых классов помещается двоеточие. Например, при таком определении

c l a s s

S :

X ,

Y ,

Z

{ . . . } ;

класс s порожден классами X,Y,Z, откуда он наследует компоненты. Наследование компонента не выполняется, если его имя будет использовано в качестве имени компонента в определении производного класса s. Как уже говорилось, по умолчанию из базовых классов наследуются методы и данные со спецификаторами доступа - public (общедоступные) и protected (защищенные).

В порожденном классе эти унаследованные компоненты получают статус доступа private, если новый класс определен с помощью ключевого слова class, и статус доступа public, если новый класс определен

340

Язык Си++

каг структура, т.е. с помощью ключевого слова struct. Таким образом, при определении класса struct J: х, z { . . . } ,• любые наследуемые компоненты классов х, z будут иметь в классе J статус общедоступных (public). Пример:

class В ( protected: int t; public: char u;

class E: В { ); // t, u наследуются как private struct S: В { ); // t, u наследуются как public

Явно изменить умалчиваемый статус доступа при наследовании можно спомощью спецификаторовдоступа-private, protected иpublic. Эти спецификаторы доступа указываются в описании производного класса непосредственно перед нужными именами базовых классов. Если класс в определен так, как показано выше, то можно ввести следующие производные классы:

class M: protected В ( . . .

class P: public В { . . . } ; class D: private В { . . . ), 1

struct F: private В { . . . } ;

struct G: public В { . . . );

}; // t, u наследуется как

//protected

//t - protected, u - public

//t, u наследуется как

//private

//t, u наследуются как

//private

l i b - protected, u - public

Соглашения о статусах доступа при разных сочетаниях базового и производного классов иллюстрирует табл. 10.1.

Обратите внимание на тот факт, что ни базовый класс, ни производный не могут быть объявлены с помощью ключевого слова union. Таким образом, объединения не могут использоваться при построении иерархии классов.

Чтобы проиллюстрировать некоторые особенности механизма наследования, построим на основе класса point (см. п. 9.4) производный класс spot (пятно). Наследуемые компоненты класса point:

int х, у

-

координаты точки на экране;

point()

-

конструктор;

givex(), givey()

-

доступ к координатам точки;

show()

-

изобразить точку;

move()

-

переместить точку.

Дополнительно к наследуемым компонентам в класс spot введем: радиус пятна (rad); его видимость на экране (vis = о, когда изоб-

Глава 10. Наследование и другие возможности классов

341

 

 

 

Таблица10.1

 

Статусы доступа при наследовании

 

Доступ в базовом

Спецификатор

Доступ в производном классе

классе

доступа перед

 

 

 

базовым классом

 

 

 

 

struct

class

public

отсутствует

public

private

protected

отсутствует

public

private

private

отсутствует.

недоступны

недоступны

public

public

public

public

protected

public

protected

protected

private

public

недоступны

недоступны

public

protected

protected

protected

protected

protected

protected

protected

private

protected

недоступны

недоступны

public

private

private

private

protected

private

private

private

private

private

недоступны

недоступны

ражения нет на экране, vis « 1 - изображение есть на экране); признак сохранения образа в оперативной памяти (tag = 0 - битовый образ не хранится, tag == 1 - битовый образ хранится в памяти); указатель pspot на область памяти, выделенную для хранения битового образа изображения.

//SPOT.CPP - класс, наследующий данные и методы

//класса POINT

#ifndef SPOT idefine SPOT 1

#include "point.cpp" // Определение класса point class spot:

//'public' позволит сохранить статусы доступа для

//наследуемых компонентов класса POINT:

public point

{ // Статус доступности данных в производных классах:

protected:

 

int rad;

// Радиус пятна (изображения)

int vis;

// Видимость пятна на экране

int tag;

// Признак сохранения образа в памяти

void *pspot;

// Указатель на область памяти для

 

// изображения (для битового образа)

// Изобразить пятно на экране
// Убрать старое изображение с экрана

342

Язык Си++

public:

//Конструктор класса SPOT: spot(int xi, int yi, int ri):

//Вызов конструктора базового класса:

 

 

 

point(xi,yi)

{

int size;

 

 

vis = 0; tag = 0; rad = ri ;

 

// Определить размеры битового образа:

 

size = imagesize(xi-ri,yi-ri,xi+ri,yi+ri);

 

// Выделить память для битового образа:

 

pspot = new char[size];

-spot()

// Деструктор класса SPOT

{

hide();

// Убрать с экрана изображение пятна

 

tag = 0 ;

// Сбросить признак сохранения в памяти

 

delete pspot

// Освободить память, где находился

 

 

 

// битовый образ

void show()

// Изобразить пятно на экране дисплея

{

// Если битового образа нет в памяти:

 

if (tag = 0)

 

 

{ // Нарисовать окружность на экране:

 

 

circle(x,у,rad);

 

 

// Закрасить пятно

 

 

floodfill(x,y,getcolor());

 

 

// Запомнить битовый образ в памяти:

 

 

getimage(x-rad,y-rad,x+rad,y+rad,pspot);

 

 

tag = 1;

 

 

)

 

 

else

 

 

// Перенести изображение из памяти на экран:

 

 

putimage(x-rad,y-rad,pspot,X0R_PUT);

 

v i s

1;

 

void

hide()

// Убрать с экрана изображение пятна

{ if (vis s=0)

// Нечего убирать

 

 

return;

 

// Стереть изображение с экрана: putimage(x-rad,y-rad,pspot,XOR_PUT); vis = 0;

}

// Переместить изображение:

void move(int xn,

int yn)

( hide();

// Убрать старое изображение с экрана

// Изменить координаты центра пятна:

Глава 10. Наследование идругие возможности классов

343

х = хп; у

уп;

 

 

show();

// Вывести изображение в новом месте

 

 

 

)

// Изменить размер изображения пятна: void vary(float dr)

{ float a; int size; hide(); tag = 0;

//Освободить память битового образа: delete pspot;

//Вычислить новый радиус:

а = dr * rad;

if (a <= 0) rad = 0; else rad = (int)a;

//Определить размеры битового образа: size = imagesize(x-rad,y-rad,x+rad,y+rad);

//Выделить память для нового образа:

new char[size]; show();

}

intb giver(void) // Доступ к радиусу пятна { return rad; )

};

#endif

Вклассе spot явно определены конструктор, деструктор -spot О

ипять методов:

show ()

вывести на экран изображение пятна, затем перенести его

 

битовый образ в память;

hide ()

убрать с экрана изображение пятна; •

move ()

переместить изображение в другое место на экране;

vary ()

изменить (уменьшить или увеличить) изображение на экра-

 

не;

giver ()

обеспечить доступ к радиусу пятна.

Из класса point класс spot наследует координаты (х, у) точки (центра пятна) и методы givex (), givey (). Методы point: : show (), point: :move() заменены в классе spot новыми функциями с такими же именами, а функция point: :hide О не наследуется, так как в классе point она имеет статус собственного компонента (private).

Конструктор spot () имеет три параметра - координаты центра (xi, yi) и радиус пятна на экране (ri). При создании объекта класса

344

Язык Си++

spot вначале вызывается конструктор класса point, который по значениям фактических параметров, соответствующих xi, yi, определяет точку - центр пятна. Эта точка создается как безымянный объект класса point. (Конструктор базового класса всегда вызывается и выполняется до конструктора производного класса.) Затем выполняются операторы конструктора spot О. Здесь устанавливаются начальные значения признаков vis, tag, и по значению фактического параметра, соответствующего формальному параметру ri, определяется радиус пятна rad. С помощью стандартной функции imagesizeO из графической библиотеки GRAPHICS . LIB вычисляется объем памяти (вспомогательная переменная size), требуемый для сохранения прямоугольного (квадратного) участка экрана, на котором предполагается изобразить пятно. Выделение участка основной памяти нужного объема выполняет стандартная операция new, операнд которой - это массив типа char из size элементов. Выделенная память связывается с указателем pspot, имеющим в классе spot статус protected. На этом работа конструктора заканчивается.

В функциях show () - изобразить пятно на экране, vary () - изменить размер изображения и hide() - убрать изображение пятна с экрана используются возможности графических функций:

circle(х,у,rad)

нарисовать окружность с центром в точке с координатами (х, у) и радиусом rad;

floodfill(x,y,c)

закрасить ограниченную область, которой принадлежит точка с координатами (х, у), цветом, определенным параметром с;

getcolor()

определить текущий цвет изображений; getimage(xl,yl,x2,y2,pnt)

поместить в заранее выделенный участок основной памяти, связанный с указателем pnt, битовый образ прямоугольного участка экрана, выделенного координатами левого верхнего (xl, yl) и правого нижнего (х2, у2) углов;

putimage(xl,yl,pnt,op)

изобразить на экране битовый образ, ранее сохраненный в памяти С помощью функции getimage (); xl, yl - координаты размещения на экране левого верхнего угла, pnt - указатель на область памяти, где хранится нужное изображение; ор - параметр, определяющий правила выбора цвета для каждого изображаемого пикселя. Выбор цвета осуществляется с учетом

Глава 10. Наследование и другие возможности классов

345

имеющегося на экране пикселя и сохраненного в памяти. Параметр ор определяет правило сочетания этих цветов в соответствии с табл. 10.2. Необходимо обратить внимание на одну особенность режима XOR_PUT. ЕСЛИ В ЭТОМ режиме изображение вывести на экран в то же место, где уже было то же самое изображение, то изображение исчезнет с экрана. Именно так убирает с экрана пятно функция hide (). Флажок видимости пятна на экране vis необходим для распознавания необходимости повторного применения функции putimage ().

Функция show () выполняется в разных режимах в зависимости от значения признака tag записи изображения в память. Если значение tag равно 0, то рисуется и закрашивается окружность, затем ее образ переписывается в память функцией getimage () и устанавливается в 1 значение tag. Если значение tag равно 1, то образ переносится на экран из той области основной памяти, где он сохранялся,- при помощи функции putimage ().

Действия функции move () понятны из комментариев в тексте про-

граммы.

Таблиц10.2

Правила выбора цвета при размещении на экране битового образа с помощью функции putimage ()

Значение

Условное обозначение

Смысл преобразования

параметра ОР

в graphics.h

 

0

COPY PUT

Копия без всяких условий

1

XOR PUT

Исключающее ИЛИ

2

OR PUT

Включающее ИЛИ (дизъюнкция)

3

AND_PUT

Логические И (коныонкция)

4

NOT PUT

Копия с инверсией изображения

Особенностью функции vary () является необходимость не только изменить размеры изображения, но и заново сохранить его битовый образ в основной памяти, При изменении размеров изображения нужно изменять и размеры памяти для его образа.

Прежде чем привести пример программы с классом spot, необходимо ввести дополнительные сведения о деструкторах.

Особенности деструкторов. Итак, конструктор вызывается при создании каждого объекта класса и выполняет все необходимые операции как для выделения памяти для данных объекта, так и для ее инициализации. Когда объект уничтожается при завершении про-

// Закрыть графический режим // Конец программы

346

Язык Си++

граммы или при выходе из области действия определения соответствующего класса, необходимы противоположные операции, самая важная из которых - освобождение памяти. Эти операции могут и должны выполняться по-разному в зависимости от особенностей конкретного класса. Поэтому в определение класса явно или по умолчанию включают специальную принадлежащую классу функцию - деструктор. Деструктор имеет строго фиксированное имя вида:

~имя_класса

У деструктора не может быть параметров (даже типа void), и деструктор не имеет возможности возвращать какой-либо результат, даже типа void. Статус доступа деструктора по умолчанию public (т.е. деструктор доступен во всей области действия определения класса).

В несложных классах деструктор обычно определяется по умолчанию. Например, в классе point деструктор явно не определен, и компилятор предполагает, что он имеет вид

~point() { };

В классе spot деструктор явно определен:

~spot() { hide(); tag = 0; delete И pspot; }

Его действия: убрать с экрана изображение пятна, обратившись к функции spot:-.hide(); установить в нуль признак tag наличия в памяти битового образа пятна; освободить память, выделенную при создании объекта для битового образа пятна и связанную с конкретным экземпляром указателя pspot.

Деструкторы не наследуются, поэтому даже при отсутствии в производном классе (например, в классе spot) деструктора он не передается из базового (например, из point), а формируется компилятором как умалчиваемый со статусом доступа public. Этот деструктор вызывает деструкторы базовых классов. В рассматриваемом примере это будет выглядеть примерно так:

public: ~spot() { ~point(); }

В любом классе могут быть в качестве компонентов определены другие классы. В этих классах будут свои деструкторы, которые при уничтожении объекта охватывающего (внешнего) класса выполняются после деструктора охватывающего класса.

Деструкторы базовых классов выполняются в порядке, обратном перечислению классов в определении производного класса. Таким

Глава 10. Наследование и другие возможности классов

347

образом порядок уничтожения объекта противоположен по отношению к порядку его конструирования.

Вызовы деструкторов для объектов класса и для базовых классов выполняются неявно и не требуют никаких действий программиста. Однако вызов деструктора того класса, объект которого уничтожается в соответствии с логикой выполнения программы, может быть явным. Это может быть, например, случай, когда при создании объекта для него явно вьщелялась память. Примером целесообразности явного вызова деструктора может служить класс spot.

Объяснив основные принципы работы деструкторов, приведем программу для работы с объектами класса spot:

//Р10-01.ГРР - наследование классов и их деструкторы #include <graphics.h> // Свяаь с графической библиотекой iinclude <conio.h> // Прототип функции getch() #include "spot.cpp" // Определение класса spot

void main()

{ // Переменные для инициализации графики: int dr ас DETECT, mod;

// Инициализация графической системы:

initgraph(£dr,£mod,"c:\\borlandc\\bgi");

{ / / В этом блоке создаются и используется об>ъекты // класса spot

spot A(200,50,20); // Создается невидимое пятно А

spot D(500,200,30);

// Создается невидимое пятно D

A.show();

// Изобразить пятно А на экране

getch();

// Ждать нажатия клавиши

D.show();

// Изобразить пятно D на экране

getch(); A.move(50,60); // Переместить пятно А

getchO; D.varyO) ;

// Изменить размеры пятна D

getch();

// Ждать нажатия клавиши

}

//При выходе из блока для каждого обгьекта автоматически

//вызывается деструктор, освобождающий выделенную

//память

closegraph();

)

Изменение состояний экрана при выполнении программы иллюстрирует рис. 10.2.

Принципиальным отличием этой программы от приведенных выше программ Р9-04.СРР, Р9-И.СРР для работы с объектами класса point является наличие внутреннего блока, что связано с наличием в классе spot деструктора, при выполнении которого вызывается ком-

348

Язык Си++

понентная функция hide О, использующая функции графики. Эти функции могут выполняться только в графическом режиме, т.е. до выполнения функции closegraph()• Если построить программу без внутреннего блока так же, как упомянутые программы с классом point, то деструктор по умолчанию будет вызываться только при окончании программы, когда графический режим уже закрыт и выполнение любых графических функций невозможно. Указанной ошибочной ситуации можно избежать двумя путями: либо вызывать деструктор явно для уничтожения объектов А И D, а потом закрывать графический режим, либо после инициализации графики ввести внутренний блок, в котором определены объекты А И D И при выходе из которого они уничтожаются, для чего деструктор дважды вызывается автоматически. Графический режим закрывается во внешнем блоке, когда объекты A, D уже уничтожены и обращения к деструктору ~spot () не нужны. В программе реализовано второе решение.

Рис. 10.2. Последовательность изображений на экране при выполнении программы Р10-01 .СРР

В качестве несложного упражнения можно удалить скобки • {)', выделяющие внутренний блок, и убедиться, что при выполнении измененной программы будет выдаваться сообщение об ошибке в графической системе:

BGI Error: Graphics not initialized (use 'initgraph')

Второе решение - явный вызов деструкторов без добавления вложенного блока:

getch() ; D.vary(3) ;

getchO;

A.spot::~spot() getchO ;

D.spot::~spot() closegraph();

//Изменить размеры пятна D

//Ждать нажатия клавиши

//Уничтожить объект А

//Ждать нажатия клавиши

//Уничтожить объект D

//Закрыть графический режим

//Конец программы

Глава 10. Наследование и другие возможности классов

349

В данном варианте класса spot при уничтожении объекта с помощью деструктора его изображение удаляется с экрана функцией hide().

10.2. Множественное наследование и виртуальные базовые классы

Класс называют непосредственным (прямым) базовым классом

(прямой базой), если он входит в список базовых при определении класса. В то же время для производного класса могут существовать косвенные или непрямые предшественники, которые служат базовыми для классов, входящих в список базовых. Если некоторый класс А является базовым для в и в есть база для с, то класс в является непосредственным базовым классом для с, а класс А - непрямой базовый класс для с (рис. 10.3). Обращение к компоненту аса, входящему в А и унаследованному последовательно классами в и с , можно обозначить в классе с либо как А: :ха, либо как в: :ха. Обе конструкции обеспечивают обращение к элементу ха класса А.

А (базовый класс - прямая база для в)

Т

в (производный от А класс - прямая база для с)

Т

с (производный класс - с прямой базой в и косвенной А)

Рис. 10.3. Прямое и косвенное наследование классов

Иерархию производных классов удобно представлять с помощью направленного ациклического графа (НАГ), где стрелкой изображают отношение "производный от". Производные классы принято изображать ниже базовых. Именно в таком порядке их объявления рассматривает компилятор и их тексты размещаются в листинге программы. Класс может иметь несколько непосредственных базовых классов, т.е. может быть порожден из любого числа базовых клаесов, например,

c l a s s XI {

. . . } ;

c l a s s

X2 {

. . .

);

c l a s s

ХЗ {

. . . );

c l a s s Yl: p u b l i c

XI, p u b l i c X2, p u b l i c X 3 { . . . } ;

// Координаты центра // Радиус окружности
// Цвет фона // Цвет изображения
// Сменить цвет рисования // Рисуем квадрат цветом фоиа
// Восстановить цвет изображения

350

Язык Си++

Наличие нескольких прямых базовых классов называют множественным наследованием. В качестве примера рассмотрим производный класс "окружность, вписанная в квадрат". Базовыми классами будут: окружность (circ) и квадрат (square). Приведем вначале их определения:

//CIRC.CPP - определение класса "окружность" iinclude <graphics.h>

class circ

( int xc, ус, re;

public:

// Конструктор:

circ(int xi, int yi, int ri)

{ xc = xi; yc = yi; re = ri; }

//Изобразить окружность на экране: void show()

{ circle(xc,yc,re) ; )

//Убрать с экрана изображение окружности:

void hide()

 

{ intЫс, ее;

 

Ыс к getbkcolor();

// Цвет фона

ее = getcolor();

// Цвет изображения

setcolor(Ыс);

// Сменить цвет рисования

//Рисуем окружность цветом фока: circle(хс,ус,гс);

//Восстановить цвет изображения: setcolor(ее);

)

//SQUARE.CPP - определение класса "квадрат"

iinclude

<graphics.h>

 

 

class square

 

 

( int xq,

yq,

//

Координаты центра квадрата

 

lq;

//

Длина стороны квадрата

/' Вспомогательная функция рисования: rissquare(void)

{int d = lq/2; line(xq-d,yq-d,xq+d,yq-d); line(xq-d,yq+d,xq+d,yq+d); line(xq-d,yq-d,xq-d,yq+d); line(xq+d,yq-d,xq+d,yq+d);

)

public:

Глава 10. Наследование и другие возможности классов

351

// Конструктор:

square(int xi,int yi,int li)

{ xq m xi; yq = yi; lq = li; )

//Изобразить квадрат на экране: void show()

( rissquare(); )

//Убрать с экрана изображение квадрата void hide()

{ intЫс, се;

Ыс = getblccolor ()

ее = getcolor(); setcolor (Ыс); rissquare(); setcolor(се);

В следующей программе на основе классов circ и square создан производныйкласс"окружностьвквадрате" сименемcircsqrt:

//Р10-02.СРР - окружность в квадрате - множественное

//наследование

#inciude <conio.h>

//

Для функции getch()

 

iinclude

"square.epp"

//

Определение класса

"квадрат"

iinclude

"circ.cpp"

//

Определение класса

"окружность"

//Производный класс - "окружность, вписанная в квадрат";

//Класс circsqrt наследует только методы обоих базовых

//классов. В нем нет наследуемых данных.

class circsqrt : public circ, public square

{public:

//Конструктор:

circsqrt(int xi, int yi, int ri):

circ(xi,yi,ri), - // Явно вызываются конструкторы О square(xi,yi,2*ri) // базовых классов

// Изобразить на экране окружность в квадрате: void show(void)

{circ::show(); square::show(); } // Убрать с экрана изображение: void hide ()

{square::hide (); circ::hide О; )

>;

void main()

{ int dr = DETECT, mod; initgraph(tdr,Gmod,"c:WborlandcWbgi"); circsqrt Al(100,100,60) ;

352

Язык Си++

circsqrt F4 (400,300,50); Al.showO ; getch() ; F4.show() ; getchO ; F4.hide() ; getchO ; Al.hideO ; getchO ; closegraph();

}

Определения базовых классов должны предшествовать их использованию в качестве базовых. Поэтому тексты из файлов square. cpp и circ.cpp включены в начало программы, после чего описывается класс circsqrt. В производном классе circsqrt телом конструктора служит пустой оператор. Выполнение конструктора ciresqrto сводится к последовательному вызову конструкторов базовых классов. При этом за счет соответствующего выбора параметров центры обеих фигур (квадрата и окружности) совпадают. Кроме того, длина стороны квадрата выбирается равной удвоенному радиусу окружности (параметр 2 * гл.), и тем самым окружность оказывается вписанной в квадрат.

В основной программе формируются два объекта Al, F4 класса circsqrt. Они последовательно выводятся на экран дисплея (рис. 10.4) и в обратном порядке убираются с экрана.

п

Рис. 10.4.Последовательностьизображенийнаэкране при выполнении программы Р10-02. СРР

При множественном наследовании никакой класс не может больше одного раза использоваться в качестве непосредственного базового. Однако класс может больше одного раза быть непрямым базовым классом:

c l a s s

X

{ . . . ;

f ( ) ; . . . ) ;

 

 

c l a s s

t :

p u b l i c X

{

. . .

} ;

 

 

c

l

a

s s

Z :

p u b l i c X

{

. . .

} ;

 

 

c

l

a

s s

D :

p u b l i c Y ,

p u b l i c

Z {

. . .

) ;

Глава10.Наследованиеидругиевозможностиклассов

353

В данном примере класс х дважды опосредованно наследуется классом D. Особенно хорошо это видно в направленном ациклическом графе (НАГ):

Проиллюстрированное дублирование класса соответствует включению в производный объект нескольких объектов базового класса. В нашем примере существуют два объекта класса х, и поэтому для устранения возможных неоднозначностей вне объектов класса D нужно обращаться к конкретному компоненту класса х, используя полную квалификацию: D::T::X::f() ИЛИ D: :Z: :X: :f (). Внутри объекта класса D обращения упрощаются: Г: :Х: :f () или Z: :X: :f (), но тоже содержат квалификацию.

В качестве содержательного примера с дублированием непрямого базового класса рассмотрим программу, в которой определен класс spotelli - круглое пятно, вписанное в эллипс. Класс spotelli непосредственно базируется на классах spot и ellips, каждый из которых базируется, в свою очередь, на классе point. Таким образом, point дважды входит в spotelli в качестве непрямого базового класса, т.е. дублируется. Класс point (точка на экране) уже рассматривался. Текст его определения находится в файле point, cpp (см. п. 9.4). Определение производного от класса point класса spot (круглое пятно на экране) находится в файле spot. cpp (см. п. 10.1). На базе класса point можно следующим образом определить класс "эллипс":

//ELLIPS.CPP - класс "эллипс" #ifndef ELLIPS

#define ELLIPS 1

•include "point.cpp"

// Определение класса point

class ellips : public point

{ protected: int rx,ry; // Радиусы эллипса

public:

 

// Конструктор:

 

ellips (int xc, int yc, int rx, int ry):

 

point (xc,yc)

{ this->rx = rx;

this->ry = ry; }

void show()

// Изобразить на экране эллипс

{ellipse(x,y,0,360,rx,ry);

return;

// Убрать с экрана изображение эллипса:

23-

354

Язык Си++

void hide()

{int со, Ыс;

ее « getcolor(); Ыс = getbkcolor();

setcolor(Ыс);

ellipse(x,y,0,360,rx,ry)

setcolor(ее);

«•ndif

Как уже отмечалось, определение базового класса должно предшествовать его упоминанию в списке базовых классов. Поэтому в начале текстов «pot.cpp и «llips.cpp помещена препроцессорная директива включения текста определения класса point.

В классе ellips конструктор предусматривает задание четырех параметров: координаты центра (хс, ус) и радиусы вдоль осей (гх, гу). Координаты хс, уе используются в качестве параметров при вызове конструктора базового класса point. Чтобы различить компоненты гх, гу класса «Hips и обозначенные теми же идентификаторами формальные параметры конструктора, используется указатель this. В классе «Hips две общедоступные функции show о - изобразить эллипс на экране дисплея; hida() - убрать с экрана изображение эллипса. Текст программы:

//Р10-03.СРР - круглое пятно в эллипсе - множественное

//наследование с дублированием базовых

//классов (дублируется класс point)

#include "spot.cpp" •include "ellips.cpp"

//Производный класс - дважды косвенно наследующий

//класс point:

class spotelli: public spot, public ellips ( // Вспомогательная функция:

int min(int valuel, int value2)

{ return ( (valuel < value2) ? valuel : value2); }

public:

// Конструктор:

spotelli(int xi,int yi,int rx,int ry) : ellips(xi,yi,rx,ry), spot(xi,yi,min(rx,ry))

{)

// Вывести изображение на экран дисплея: void show()

{ spot::show();

Глава 10. Наследование и другие возможности классов

355

ellips::show();

)

void hide() // Убрать изображение с экрана дисплея

{spot::hide();

ellips::hide();

iinclude <conio.h> // Для функции getch() void main()

{ int dr = DETECT, mod; initgraph(fidr,&nod,"c:\\borlandc\\bgi"); { spotelli Al (100,100,20,80);

spotelli F4(400,300,230,100); Al.show(); getch(); F4.show(); getch(); F4.hide(); getch(); Al.hideO;

}

closegraph();

В классе ellips, в классе spot и в классе spotelli наследуются данные х, у класса point - координаты точки на экране. В классе point они определены как защищенные (protected) и сохраняют тот же статус доступа в производных классах, где определяют координаты центровпятна (класс spot), эллипса (класс ellips) и эллипса с пятном (класс spotelli).

Класс spot мы уже разбирали.

Рис. 10.5. Последовательность изображений на экране при выполнении программы Р10-03. СРР

Конструктор класса spotelli не выполняет никаких дополнительных действий - последовательно вызываются конструкторы класса ellips и класса spot, причем центры создаваемых фигур совпадают, а в качестве радиуса пятна выбирается меньший из радиусов эллипса. Используемая в этом случае функция min () определена

23*