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

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

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

356 Язык Си++

по умолчанию как встроенная (inline) собственная (private) функция класса spotelli.

Чтобы отличать одинаково обозначенные функции, унаследованные классом spotelli из классов spot и ellips, при вызове show() и hide () используются полные квалифицированные имена, в которых применена операция ' : : ' .

Функция main () не содержит ничего нового. Описаны два объекта Al, F4 класса spotelli, которые последовательно изображаются на экране и "стираются" с него.

Чтобы устранить дублирование объектов непрямого базового класса при множественном наследовании, этот базовый класс объявляют виртуальным. Для этого в списке базовых классов перед именем класса необходимо поместить ключевое слово virtual. Например, класс х будет виртуальным базовым классом при таком описании:

class X { . . . f ( ) ; . . . };

class Y: virtual public X { .. . }; class Z: virtual public X { . . . }; class D: public X, public Z { .. . };

Теперь класс D будет включать только один экземпляр х, доступ к которому равноправно имеют классы Y и z. Графически это очень наглядно:

Иллюстрацией сказанного может служить иерархия классов в следующей программе:

//Р10-04.СРР - множественное наследование с виртуальным

//

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

#include <io3tream.h>

class base // Первичный (основной) базовый класс { int jj; char cc; char w[10] ;

public:

base(int j • 0, char e = '•')

{jj = j ; cc = c;

class dbase: public virtual base { double dd;

public:

dbase(double d = 0.0) : base ()

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

357

{ dd • d; }

);

class fbase: public virtual base { float ff;

public:

fbase(float f = 0.0): base() { ff - f; )

};

class top: public dbase, public fbase { long tt;

public:

top(long t = 0) : dbase (), fbase() { tt - t; )

);

void main()

{ cout « "ХпОсновной базовый класс: sizeof(base) = " « sizeof(base);

cout « " ^Непосредственная база: sizeof(dbase) «= " « sizeof(dbase);

cout « "ХпНепосредственная база: sizeof(fbase) = " « sizeof(fbase);

cout « "ХпПроизводный класс: sizeof(top) • " « sizeof(top);

Результаты выполнения программы:

 

Основной базовый класс: sizeof(base)

13

Непосредственная

база:

sizeof(dbase)

23

Непосредственная

база:

sizeof(fbase)

19

Производный класс: sizeof(top) = 33

Основной базовый класс base в соответствии с размерами своих компонентов стандартных типов int и char [li] имеет размер 13 байт. Создаваемые на его основе классы dbase и fbase занимают соответственно 23 и 19 байт. (В dbase входит переменная типа double, занимающая 8 байт, наследуется базовый класс base, для которого требуется 13 байт, и 2 байта нужны для связи в иерархии виртуальных классов.) Производный класс top включает: один экземпляр базового класса base (13 байт); данные и связи класса dbase (10 байт); данные и связи класса fbase (6 байт); компонент long tt (4 байта).

Если в той же программе убрать требование виртуальности (атрибут virtual) при наследовании base в классах dbase и fbase, то результаты будут такими:

358

Язык Си++

Основной базовый класс: sizeof(base) =• 13 Непосредственная база: sizeof(dbase) = 21 Непосредственная база: sizeof(fbase) = 17 Производный класс: sizeof(top) = 42

Обратите внимание, что размеры производных классов при отсутствии виртуальных базовых равны сумме длин их компонентов и длин унаследованных базовых классов. "Накладные расходы" памяти здесь отсутствуют.

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

tв тI

class X { . . . } ;

class Y: virtual public X ( class Z: virtual public X { class B: virtual public X ( ' class C: virtual public X { class E: public X { ... ); class D: public X { ... ); class A: public D, public B,

public Y, public Z, public C, public E

Z

с

Е

t

t

t

 

{

В данном примере объект класса А включает три экземпляра объектов класса х: один виртуальный, совместно используемый классами в, у, с, z, и два невиртуальных относящихся соответственно к классам D и Е. Таким образом, можно констатировать, что виртуальность класса в иерархии производных классов является не свойством класса как такового, а результатом особенностей процедуры наследования.

Возможны и другие комбинации виртуальных и невиртуальных базовых классов. Например:

c l a s s В В ( . . . } ;

class АА: virtual public ВВ { ... }; class CC: virtual public ВВ ( ... };

class DD: public AA, public CC, public virtual BB { ... );

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

359

Соответствующий НАГ имеет вид:

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

c l a s s

X

(

public:

i n t

d; . . .

};

c l a s s

У

{

public:

i n t

d; . . .

};

clas s Z: public X, public Y, ( public:

in t d;

d - X::d + Y::d;

10.3. Виртуальные функции и абстрактные классы

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

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

360

Язык Си++

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

Рассмотрим теперь, как ведут себя при наследовании невиртуальные компонентные функции с одинаковыми именами, типами и сигнатурами параметров.

Если в базовом классе определена некоторая компонентная функция, то такая же функция (с тем же именем, того же типа и с тем же набором и типами параметров) может быть введена в производном классе. Рассмотрим следующее определение классов:

//BASE.DIR - определения базового и производного классов struct base {

void fun(int i)

{ cout « "\nbaae::i = " « i; )

struct dir: public base

 

{ void fun (int i)

 

{ cout « "\ndir::i

« i; )

Вданном случае внешне одинаковые функции void fun (int) определены в базовом классе base и в производном классе dir.

Втеле класса dir обращение к функции fun (), принадлежащей классу base, может быть выполнено с помощью полного квалифици-

рованного имени, явно включающего имя базового класса: base:: fun (). При обращении в классе dir к такой же (по внешнему виду) функции, принадлежащей классу dir, достаточно использовать имя fun <) без предшествующего квалификатора.

В программе, где определены и доступны оба класса base и dir, обращения к функциям fun О могут быть выполнены с помощью указателей на объекты соответствующих классов:

//Р10-05.СРР - одинаковые функции в базовом и производном

// классах iinclude <iostream.h>

#include "base.dir" // Определения классов void main(void)

( base B, *bp - SB; dir D, *dp = SD; base *pbd » £D;

bp->fun(l);

// Печатает : base::i " 1

dp->fun(5);

// Печатает : dir::i «• 5'

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

361

pbd->fun(4) ;

// Печатает : base::i

 

Результаты выполнения программы:

base::i = 1 dir::i = 5 base::i = 4

В программе введены три указателя на объекты разных классов. Следует обратить внимание на инициализацию указателя pbd. В ней адрес объекта производного класса (объекта D) присваивается указателю на объект его прямого базового класса (base *). При этом выполняется стандартное преобразование указателей, предусмотренное синтаксисом языка Си++. Обратное преобразование, т.е. преобразование указателя на объект базового класса в указатель на объект производного класса, невозможно (запрещено синтаксисом). Обращения к функциям классов base и dir с помощью указателей Ьр и dp не представляют особого интереса. Вызов pbd->fun() требуется прокомментировать. Указатель pbd имеет тип base*, однако его значение - адрес объекта D класса dir.

Какая же из функций base: :fun() или dir: :fun() вызывается при обращении pbd->fun()? Результат выполнения программы показывает, что вызывается функция из базового класса. Именно такой вызов предусмотрен синтаксисом языка Си++, т.е. выбор функции (не виртуальной) зависит только от типа указателя, но не от его значения. "Настроив" указатель базового класса на объект производного класса, не удается с помощью этого указателя вызвать функцию из производного класса.

Вернемся к упомянутому выше примеру с фигурой в виде базового класса с названием figure. Пусть в этом классе определена компонентная функция void show(). Так как внешний вид фигуры в базовом классе еще не определен, то в каждый из производных классов нужно включить свою функцию void show () для формирования изображения на экране. Если оставаться в рамках проиллюстрированного в примере с классами base и dir механизма, то доступ к функции show() производного класса возможен только с помощью явного указания области видимости:

имя_проиаводного_класса::show()

либо с использованием имени конкретного объекта:

имя_об1ьекта_проиаводно:го_класса. show ()

362

ЯзыкСи++

В обоих случаях выбор нужной функции выполняется при написании исходного текста программы и не изменяется после компиляции. Такойрежимназываетсяраннимилистатическимсвязыванием.

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

функций. Любая нестатическая функция базового класса может быть сделана виртуальной, если в ее объявлении использовать спецификатор virtual. Прежде чем объяснить преимущества динамического связывания, приведем пример. Опишем в базовом классе виртуальную функцию и введем два производных класса, где определим функции с такими же прототипами, но без спецификатора virtual. В следующей программе в базовом классе base определена виртуальная функция void vfun(int). В производных классах dirl, dir2 эта функция подменяется (override), т.е. определенапо-другому:

//Р10-06.СРР - виртуальная функция ж базовом классе

•include <iostream.h>

 

•include <conio.h>

 

struct base {

 

virtual void vfun(int i)

 

{ cout « "\nbaae::i • » « i;

)

} ;

 

struct dirl: public base {

 

void vffun (int i)

 

{ cout « "\ndirl::i - " « i;

}

struct dir2: public base { void vfun (int i)

{ cout « "\ndir2::i - " « i; }

void main(void)

 

( base B, *bp - CB;

 

dirl Dl, *dpl - «Dl;

 

dir2 02, *dp2 - £D2;

// Печатает: base::i = 1

bp->vfun(l);

dpl->vfun(2);

// Печатает: dirl::i = 2

dp2->vfun(3);

// Печатает: dir2::i = 3

bp ™ CD1; bp->vfun(4);

// Печатает: dirl: :i - 4

bp = 6D2; bp->vfun(5);

// Печатает: dir2: :i » S

Результат выполнения программы:

base::i « 1

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

363

dirl::i = 2 dir2::i = 3 dirl::i = 4 dir2::i = 5

В примере надо обратить внимание на доступ к функциям vf un () через указатель Ьр на базовый класс. Когда bp принимает значение адреса объекта класса base, то вызывается функция из базового класса. Затем Ьр последовательно присваиваются значения ссылок на объекты производных классов tDl, tD2, и выбор соответствующего экземпляра функции vfun () каждый раз определяется именно объектом. Таким образом, интерпретация каждого вызова виртуальной функции через указатель на базовый класс зависит от значения этого указателя, т.е. от типа объекта, для которого выполняется вызов. В противоположность этому интерпретация вызова через указатель невиртуальной функции зависит только от типа указателя (это было показано в предыдущем примере с функцией fun ()).

Виртуальными могут быть не любые функции, а только нестатические компонентные функции какого-либо класса. После того как функция определена как виртуальная, ее повторное определение в производном классе (с тем же самым прототипом) создает в этом классе новую виртуальную функцию, причем спецификатор virtual может не использоваться.

Впроизводном классе нельзя определять функцию с тем же именем и

стой же сигнатурой параметров, но с другим типом возвращаемого значения, чем у виртуальной функции базового класса. Это приводит к ошибке на этапе компиляции.

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

Сказанное иллюстрирует следующая программа.

//Р10-07.СРР - некоторые особенности виртуальных функций •include<iostream.h>

•include <conio.h> struct base {

virtual void f1(void) { cout « "\nbase::f1"; ) virtual void f2(void) { cout « "\nbase::f2"; )

364

ЯзыкСи++

virtual void f3(void) { cout « "\nbaee::f3"; )

);

struct dir: public base { // Виртуальная функция:

void fl(void) { cout « "\ndir::fl"; )

//Ошибка в типе функции:

//int f2(void) { cout « "\ndir::f2"; )

//Невиртуальная функция:

void f3(int i) { cout « "\ndir::f3::i - "« i; )

>;

void main(void)

{ base B, *pb » tB; dir D, *pd - tD;

p pb->f2(); pb->f3(>;

pd->£2();

//Ошибка при попытке бе* параметра вызвать dir::f3(int):

//pd->f3();

pd->ff3(0); pb -SD;

pb->f2();

P b->f3();

// Ошибочное употребление или параметра, или указателя: // pb->f3(3);

Результат выполнения программы:

base::f1

base::f2

base::f3

dir::fl

base::f2 dir::f3::i = 0 dir::fl base::f2

base::£3

Обратите внимание, что три виртуальные функции базового класса по-разному воспринимаются в производном классе. dir::fl( ) - виртуальная функция, подменяющая функцию base: :« . () . Функция base::f2() наследуется в классе dir так же, как и функция base: :f3() . Функция dir: :f3(int) - совершенно новая компонент-

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

365

ная функция производного класса, никак не связанная с базовым классом. Именно поэтому невозможен вызов f3(int) через указатель на базовый класс. Виртуальные функции base: :f2() и base: :f3() оказались не переопределенными в производном классе dir. Поэтому при всех вызовах без параметров f3() используется только компонентная функция базового класса. Функция d i r : :f3(int) иллюстрирует соглашение языка о том, что если у функции производного класса набор параметров отличается от набора параметров соответствующей виртуальной функции базового класса, то это не виртуальная функция, а новый метод производного класса.

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

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

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

struct base {

virtual int f(iht j) ( return j * j; }

struct dir: public base { int f(int i)

{ return base::f(i * 2); }

Абстрактные классы. Абстрактным классом называется класс, в котором есть хотя бы одна чистая (пустая) виртуальная функция. Чистой виртуальной называется компонентная функция, которая имеет следующее определение:

virtual тип имя_функции(список_формалышх__параметров) = 0;

В этой записи конструкция "= 0" называется "чистый спецификатор". Пример описания чистой виртуальной функции:

366 ЯзыкСи++

virtual void fpure(void) 0;

Чистая виртуальная функция "ничего не делает" и недоступна для вызовов. Ее назначение - служить основой для подменяющих ее функций в производных классах. Исходя из этого становится понятной невозможность создания самостоятельных объектов абстрактного класса. Абстрактный класс может использоваться только в качестве базового для производных классов. При создании объектов такого производного класса в качестве подобъектов создаются объекты базового абстрактного класса.

Предположим, что имеется абстрактный класс:

class В { protected:

virtual void func(char) = 0; void sos(int);

На основе класса в можно по-разному построить производные классы:

class D: public В {

void func(char);

};

class E: public В { void sos(int);

>;

Вклассе D чистая виртуальная функция func () заменена конкретной виртуальной функцией того же типа. Функция B::sos() наследуется классом D и доступна в нем и в его методах. Класс D не абстрактный. В

классе Е

переопределена функция B::sos(), а виртуальная функция

В: :func()

унаследована. Тем самым класс Е становится абстрактным и

может использоваться только как базовый.

Как всякий класс, абстрактный класс может иметь явно определенный конструктор. Из конструктора возможен вызов методов класса, но любые прямые или опосредованные обращения из конструктора к чистым виртуальным функциям приведут к ошибкам во время выполнения программы.

Механизм абстрактных классов разработан для представления общих понятий, которые в дальнейшем предполагается конкретизировать. Эти общие понятия обычно невозможно использовать непосредственно, но на их основе можно, как на базе, построить частные производные классы, пригодные для описания конкретных объектов.

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

367

Например, из абстрактного класса "фигура" можно сформировать класс "треугольник", "окружность" и т.д.

В качестве примера рассмотрим программу, в которой на основе базового класса point построен абстрактный класс figure. В классе figure определены: конструктор, чистая виртуальная функция show () для вывода изображения фигуры, например, на экран дисплея. Кроме того, в класс входят методы hide () - убрать изображение фигуры с экрана дисплея и move () - переместить изображение фигуры в заданную точку экрана. Функции hide () и move () обращаются к чистой виртуальной функции show(). Однако реальное выполнение show () возможно» только после создания производного класса, в котором чистая виртуальная функция show О будет подменена компонентной функцией для изображения конкретной фигуры.

Определение абстрактного класса figure (в файле figure. срр):

//FIGURE.CPP - абстрактный класс на базе класса #include "point.срр"

class figure: public point { public:

// Конструктор абстрактного класса figure: figure (point p) : point (p. givex (), p.giveyO) { )

//Чистая виртуальная функция для будущего

//изображения фигур:

virtual void show() « 0;

// Функция для удаления изображения фигуры: void hide()

( int Ыс, се;

Ыс«•getbkcolor(); се = getcolorO ; setcolor(Ыс);

show(); // Обращение к чистой виртуальной функции setcolor(ее);

void move(point p) // Перемещение фигуры в точку "р" { hide (); х • p.givexO; у ж p.giveyO; show О ; )

На базе класса figure определим неабстрактные классы:

//ELLIPS.FIG - конкретный класс "эллипс" на основе figure class ellips : public figure {

int rx,ry; public:

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

ellips (point d, int radx, int rady): figure(d)

368

Язык Си++

{rx = radx; ry = rady; } void show О

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

return;

//CIRC.FIG - конкретный класс "окружность" class circ: public figure {

int radius; public:

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

circ(point e, int rad): figure(e) { radius = rad; ) void show() { circle(x,y,radius); }

В следующей программе используются все три класса:

//Р10-08.СРР - абстрактные классы и чистые виртуальные // функции

«include <graphics.h> «include "figure.cpp" «include "circ.fig"

«include "ellips.fig"

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

{ point A(100,80), B(300,200); circ С(A, 60);

ellips E(B,200,100);

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

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

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

//Изобразить точку - point::show(): A. show(); getch();

//Изобразить точку - point::show():

B.show(); getch();

//Показать окружность - circ::show(): С.show(); getch();

//Показать эллипс - ellips::show():

E.show(); getch();

//Совместить фигуры - circ::figure::move(): С.move(B); getch();

//Убрать эллипс - ellips::figure::hide(): E.hideO ; getch() ;

//Убрать окружность - circ::figure::hideО:

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

369

C.hideO; getch() ;

}

closegraph();

Графические результаты выполнения показаны на рис. 10.6.

0 e

 

 

 

" 0

 

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

В программе на базе класса figure определены два производных класса: circ (окружность) и ellips (эллипс). Для обоих классов унаследованный класс point определяет центры фигур. В обоих классах определены конкретные методы show() и из абстрактного класса figure унаследованы функции move () и hide (). Комментарии к операторам основной программы содержат полные (квалифицированные) имена исполняемых функций.

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

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

370

Язык Си++

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

В класс chain входят (рис. 10.7) в качестве компонентов: статический указатель на последний элемент, уже включенный в список (last), статический указатель на начало списка (begin), указатель в объекте на следующий элемент списка (next), указатель в объекте на абстрактный базовый класс figure (pfig). Параметр конструктора класса chain пусть имеет тип указателя на абстрактный базовый класс figure. В качестве фактических параметров будем использовать ссылки на конкретные объекты классов, производных от абстрактного класса figure. Тем самым в односвязный список включаются (рис. 10.7) конкретные фигуры (окружность - класс eirc, эллипс - класс el lips).

begin

next

I

circ

last

| next

 

ellips

NULL

Рис 10.7. Схема односвязного списка (класс chain,/, объединяющего объекты разных классов.

Текст программы со всеми включениями и определениями:

//Р10-09.СРР - одкосвязный список объектов разных классов

«include <stdlib.h>

// NULL, malloc,...

«include <conio.h>

// getch(),...

//«include <iostream.h> // cout,...

«include "point.cpp"

// Базовый класс для figure

//Абстрактный класс, производный от point: «include "figure.cpp"

//Класс, производный от абстрактного figure: «include "circ.fig"

//Класс, производный от абстрактного figure: «include "ellips.fig"

//Объекты класса - фигуры, включенные в односвязкый

//список:

class chain { // Объект - элемент в односвязном списке // Указатель на последний элемент в списке:

static chain *last;

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

371

// Указатель в объекте на следующий элемент: chain *next;

public:

//Указатель на фигуру, входящую в элемент списка: figure *pfig;

//Указатель на начало списка:

static chain *begin;

//Конструктор: chain(figure *p);

//Функция изображает все фигуры списка: static void showAll(void);

)

It Конец определения класса

//Внешнее описание и инициализация статических

//компонентов класса:

chain *chain::begin = NULL; // Начало списка

chain

*chain::last = NULL;

 

// Последний элемент в списке

void

chain::showAll(void)

 

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

{ chain *ulc = begin;

// Настройка на начало списка

while (uk != NULL)

// Цикл до конца списка

 

( uk->pfig->show();

//

Нарисовать конкретную фигуру

 

uk = uk->next;

//

Настройка на следующий элемент

//Конструктор создает и включает в список объект,

//связав его с конкретной фигурой из класса, производного

//от абстрактного:

chain:: chain(figure *p)

// p - адрес включаемой фигуры

( if (begin = NULL)

 

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

begin = this;

 

 

 

else last->next

this;

// Связь с предыдущим элементом

pfig = p;

//

Запоминаем адрес включаемой фигуры

next = NULL;

//

Пометим окончание списка

last = this;

//

Запоминаем адрес последнего элемента

 

//

списка

void main()

{point A(100,80), В(300,200); circ С(А, 60) ;

ellips E(8,200,100);

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

//Инициализация графической системы: initgraph(fidr,&mod,"с:\\borlandc\\bgi");

A.show(); getchO; // Изобразить точку - point:: show()

B.show (); getchO; // Изобразить точку

//Показать окружность - circ::show():

24*

372 ЯзыкСи++

C.ehowO ; getchO;

// Включить в список первый элемент - окружность С: chain ca(fiC);

Б.show(); getch();

// Показать эллипс -

ellips::show()

chain се(ЬЕ);

// Включить в список

эллипс

//Совместить фигуры - circ::figure::move(): С.move(В); getchO;

//Убрать эллипс - ellips::figure::hide(): E.hideO ; getchO ;

//Убрать окружность - circ::figure::hide(): Chide() ; getchO ;

//Изобразить все фигуры из списка: chain::showAll();

getchO ;

>

closegraphO ;

 

0

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

m

_

 

 

i

 

 

 

0

 

 

 

 

 

 

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

Статическая компонентная функция chain: :showAll() обеспечивает вывод на экран изображений всех конкретных фигур, включенных в односвязный список. Важно отметить последовательность передач управления в этой функции. Указатель uk типа chain * позволяет обратиться к компоненту pf ig - указателю на абстрактный базовый класс figure. После выполнения конструктора chain () зна-

чением pfig является ссылка на объект некоторого производного от figure класса. Именно оттуда выбирается соответствующая функция show (), подменяющая чистую виртуальную функцию figure:: show (). Тем самым на экран выводится изображение кон-

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

373

кретной фигуры. Например, функция circ::show()

изобразит окруж-

ность и т.д.

 

В основной программе формируются точки А, в и на них, как на центрах, создаются объекты с (окружность) и Е (эллипс). В графическом режиме выводятся на экран и убираются с экрана изображения всех созданных объектов. Затем функцией showAll() рисуются все объекты, включенные в список. Результат выполнения программы показан на рис. 10.8.

10.4.Локальныеклассы

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

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

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

Для иллюстрации особенностей локальных классов рассмотрим следующую задачу. Пусть требуется определить класс "квадрат". Ограничимся квадратами, стороны которых параллельны осям прямоугольной декартовой системы координат. Исходными данными для задания каждого конкретного квадрата (объекта класса "квадрат") будут координаты центра и размер стороны. Внутри класса "квадрат" определим локальный класс "отрезок". Исходными данными для определения каждого конкретного отрезка будут координаты его концов. Четыре отрезка с соответствующим образом выбранными концевыми точками составят квадрат. Именно таким образом можно будет изобразить квадрат на экране (как изображения четырех отрезков).

//Р1О-10.СРР - внешние, локальные и глобальные классы #include <con±o.h>

finclude "point.cpp" // Внеимий класс "точка"

- di/2; - di/2; + di/2; - di/2;

374

 

ЯзыкСи++

class

square

// Глобальный класс "квадрат"

{ class segment

// Локальный класс "отрезок"

{

point pn, pk;

// Точки начала и конца отрезка

public:

// Конструктор отрезка

 

segment(point

pin = point(0,0),

 

point

pik ж point(0,0))

{pn.givexO ! • pin.givex(); pn.givey() ' • pin.givey(); pk.givex() > = pik. givex () ; pk.givey() > ' pik.giveyO ;

// Доступ к граничным точкам отрезка:

points beg(void)

{

return pn;

}

points end(void)

{

return pk;

)

void showSeg0 // Изобразить отрезок на экране

{ line

(pn.givexO, pn.giveyO,

 

pk.givex(), pk.givey()); )

};

// Конец определения класса segment

segment ab, be, cd, da; // Отрезки - стороны квадрата

public:

 

 

 

 

// Конструктор квадрата:

 

square (point ci ж point(0,0),

int di > 0)

{// Вершины квадрата - локальные объекты

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

point a, b, с, d; a.givex() » ci.givex() a.givey() - ci.giveyO b.givex() • ci.givex() b.givey() • ci.giveyO

с.givex() » ci.givexO + di/2; c.giveyO - ci.giveyO + di/2; d. givex() * ci.givexO - di/2; d.giveyO - ci.giveyO + di/2; // Граничные точки отрезков: ab.begO - a; ab.end() - b; bc.begO ж b; bc.endO ж С; cd.begO ж С; cd.end() = d; da.begO ж d; da.endO ж а;

void showSquare(void)

// Изобразить квадрат

( ab.showSeg0 ;

 

be.showSeg();

 

cd.showSeg();

 

da.showSeg();

 

// Конец определения класса "квадрат"

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

375

void

main О

 

{ //

Переменные для инициализации графики:

 

int

dr ж DETECT, mod;

 

// Инициализация графической системы: initgraph(tdr, (mod, "с: WborlandcWbgi") ; point pi(80,120);

point p2(250,240); square Л(pi,30); square B(p2,140);

A.showSquare(); getch(); В.showSquare0; getch(); closegraph();

Результат в графическом виде показан на рис. 10.9.

оа

Рис. 10.9. Изображенияна экране привыполнении программы Р10-10. СРР

Отметим некоторые особенности программы. Класс "квадрат" (square) включает в качестве данных четыре стороны - отрезки аЬ, be, cd, da, каждый из которых есть объект локального класса segment. Конструктор класса square о по заданным центру квадрата и размеру стороны определяет значения точек-вершин, а уже по ним формирует отрезки - стороны квадрата.

10.5. Классы и шаблоны

Шаблоны, которые иногда называют родовыми или параметризованными типами, позволяют создавать (конструировать) семейства родственных функций и классов [2], [9].

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

template <список_параметров_жаблока> определеяие_фуякции