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

Иванова Г.С. - Основы программирования

.pdf
Скачиваний:
2771
Добавлен:
02.04.2015
Размер:
13.53 Mб
Скачать

//. Иерархии классов

TRoom

15

TFlat

length, width

n, roomsrTRoom

^

Square(), Init ()

 

FlatSquare(), Init ()

Рис. 11.6. Диафамма классов примера

Физически любая квартира состоит из нескольких комнат. Ранее у нас уже был разработан класс TRoom, который хранил данные о длине и шири­ не комнаты и «умел» реагировать на запрос о площади комнаты. Количество комнат в квартире сравнительно не велико и всегда ограничено, для опреде­ ленности будем считать, что реализуемый объект не может включать более 15 комнат. Тогда разрабатываемый класс TFlat должен включать массив из 15 объектов типа TRoom (рис. 11.6). Реальное количество комнат будем хранить в поле п. Для ответа на запрос о площади добавим метод FlatSquare, который будет обращаться к методам Square объектов-комнат TRoom.

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

Program ex;

 

 

Uses Room;

{модуль Room определен в параграфе 10.3}

Туре TFlat=object

{описание класса}

n:byte;

 

{количество комнат}

rooms:array[L.15] of TRoom; {массив объектов TRoom}

function

FlatSquare:real;{MQTOJX определения площади}

procedure Init(an:byte;Var arooms);{MQTOJX инициализации}

end;

 

 

Procedure TFlatJnit; {тело метода инициализации}

Var a:array[L,15]

of TRoom absolute arooms; {переопределение типа

Ubyte;

 

массива наложением - см. параграф 5.5}

 

 

Begin

 

 

п:='ап;

{инициализируем поле количества комнат}

for i:=l to п do {инициализируем объектные поля, вызывая метод инициализации TRoom для каждой комнаты и передавая ему размеры комнат}

rooms[i].Inlt(a[i].length, а[i],width); End;

331

Часть 2, Объектно-ориентированное программирование

Function TFlaLFlatSquare; {тело метода определения площади}

Var S:real; i:integer;

Begin S:=0;

for i:=l to n do {суммируем площади комнат}

S: =S+rooms[iJ,Square;

FlatSquare:=S;

 

 

End;

 

 

Const mas:array[L.3]of

TRoom- ((length:!.5;

width:3.75),

 

(length:2.85; width:4.1),

 

(length:2.3;

width:2.8));

Var F:TFlat; {объявляем объект-переменную}

Begin

 

 

FInit(3,mas);

{инициализируем объект}

WriteLn(*Площадь квартиры= \FFlatSquare);{определяем площадь} End

Задания для самопроверки

Задание 1. Реализуйте класс, диафамма которого изображена на рис. 9.6, а. Разработайте тестирующую программу.

Задание 2. Спроектируйте класс для реализации объекта «Меню», который, по­ лучив сообщение «Изобразить меню», выводит на экран окно, включающее окна меньшего размера, содержащие строки - названия пунктов (см. пример 8.4). Разра­ ботайте тестирующую программу.

11.3. Наполнение

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

Пример 11.3. Разработать класс для реализации объекта Комната с бал­

 

 

коном, который должен реагировать на запрос

Площадь?

Комната

об общей площади (рис. 11.7).

Балкон в комнате может существовать или

 

с балконом

нет. Для простоты будем считать, что в комна­

 

 

Рис. 11.7. Объект Комната

те может быть не более одного балкона прямо-

угольной формы. Следовательно, разрабатыва-

с балконом

емый класс можно наследовать от класса

332

TRoom и включить в него указатель на объект

0..1

TRoom

класса TRoom (рис. 11.8). Он должен добавлять

 

length, width

//.

Иерархии классов

 

 

свои методы определения площади и инициали­

 

Square(), Init ()

зации объектов, учитывающие наличие или от­

 

 

I

сутствие балкона.

 

 

 

 

 

Program ex;

 

 

TBRoom

 

 

pB:^TRoom

Uses Room;

 

 

 

 

BSquare(), InitAII ()

Type

 

 

 

 

 

TBRoom =object(TRoom)

Рис. 11.8. Диаграмма

pB: ^TRoom;

 

 

классов для TBRoom

function BSquare:real;

 

 

procedure InitAll(lw:real; lb,wb:real);

 

 

end;

 

 

 

Procedure TBRoom. InitAII;

 

 

Begin

 

 

 

Init(l,w);

 

 

 

if (Ib^O) or (wb-=OJ

then

 

 

pB—nil

 

 

 

else

 

 

 

begin

 

 

 

New(pB);

 

 

 

pB''Jnit(lb,wb);

end;

End;

Function TBRoom,BSquare; Begin

ifpB=nil then BSquare: = Square;

else BSquare: = Square^pB\ Square;

End;

Var B:TBRoom; {объявляем объект-переменную}

Begin

В, InitAll(3J,5J,L8A8); (инициализируем объект}

WriteLn('Площадь комнаты с балконом = \B.BSquare); End

Процесс выделения памяти под динамические поля объекта необходимо контролировать. С этой целью используют средства контроля выделения па­ мяти, рассмотренные в параграфе 7.2. Кроме этих средств существуют спе­ циальные средства контроля выделения памяти под динамические поля, ко­ торые обычно используют при разработке конструкторов. Эти средства и приемы их использования будут рассмотрены в параграфе 11.7.

333

Часть 2. Объектно-ориентированное программирование

Задания для самопроверки

Задание 1. Реализуйте класс, диаграмма которого изображена на рис. 9.6, б. Разработайте тестирующую программу.

Задание 2. Спроектируйте класс для реализации объекта «Настраиваемое ме­ ню», который, получив сообщение «Изобразить меню», выводит на экран окно, включающее окна меньшего размера, содержащие строки - названия пунктов (см. пример 8.4). Объект должен создаваться в процессе работы программы. Количество пунктов меню и заголовки должны вводиться с клавиатуры. Разработайте тестирую­ щую программу.

11.4. Простой полиморфизм

Как уже говорилось в параграфе 9.3, возможность переопределения ме­ тодов при наследовании является частным случаем полиморфизма.

Пример 11.4. Разработать класс для реализации объекта Трехмерная комната 2, который должен реагировать на запрос о суммарной площади стен и потолка (рис. 11.9).

Класс, реализующий данный объект, будем наследовать от класса TRoom. Этот класс должен включать метод определения площади стен и по­ толка. Логично назвать этот метод Square, но метод с таким именем уже оп­ ределен в базовом классе. Поскольку объект не должен реагировать на за­ прос о площади комнаты, метод Square базового класса можно переопреде­ лить (рис. 11.10).

Помимо переопределения метода Square класс TVRoom2 переопределя­ ет метод инициализации полей объекта Init, добавляя определение значения нового поля height.

В Borland Pascal сохраняется возможность обращения к переопределен­ ному родительскому методу из методов класса-потомка. С этой целью перед

именем метода указывают служебное слово inherited

или имя класса-родите­

ля и точку:

 

 

 

inherited Square или

TRoom. Square

 

Окончательно получаем следующую программу:

 

Площадь?

Трехмерная

TRoom

TVRoom2

length, width

height

 

комната 2

 

Square(), Init()

Square(), Init()

 

 

Рис. 11.9. Объект

Рис. 11.10. Иерархия классов

Трехмерная комната 2

для класса TVRoom

334

//. Иерархии классов

Program ex; Uses Room;

Type TVRoom2 = object(TRoom)

height;real; {дополнительное поле класса} function Square:real; {переопределенный метод класса} procedure InU(l,w,h:real); {переопределенный

инициализирующий метод}

end;

Procedure TVRoom2,Init; Begin

{инициализируем поля базового класса} inherited InitQyW); {или TRooniInit(l,w) }

height:-h; {инициализируем собственное поле класса}

End;

Function TVRoom2.Square; Begin

Square: ^inherited Square-^2'^height*(length'^width); {обращаемся

к переопределенному методу базового класса}

End;

VarA:TVRoom2; Begin

AJnit(3A5.l2.8);

WriteLnCrinouiadb стен и потолка = \A.Square); End

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

Списки параметров статических полиморфных методов могут разли­ чаться. Так, в рассмотренном выше примере метод Init класса TRoom имеет два параметра, а метод Init класса TVRoom2 - три параметра.

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

<имя базового класса>(<имя объекта производного класса>).<имя метода>.

335

Часть 2. Объектно-ориентированное

программирование

Например:

VarA: TVRoom2;,.,

TRoom(A),Square;.. {вызываем метод базового класса}

Такое переопределение типа в ООП нгзы^^гют восходящим приведением

типа в отличие от нисходящего^ которое используется, если требуемые мето­ ды или поля производного класса при обращении к объекту того же класса через указатель базового класса не видны (рис. 11.4). Восходящее приведе­ ние типа возможно всегда, в то время как при выполнении нисходящего при­ ведения необходимо быть уверенным, что в данный момент времени указа­ тель действительно содержит адрес объекта производного класса или его по­ томков.

11.5. Сложный полиморфизм. Конструкторы

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

Пример 11.5, Разработать классы для реализации объекта Комната П, который должен отвечать на запрос о площади пола, выводя результат сразу на экран, и объекта Трехмерная комната П, который должен отвечать на за­ прос о площади стен и потолка, также выводя результат на экран (рис. 11.11).

Класс TRoom2 строим аналогично классу TRoom, добавив метод выво­ да результата на экран Print. Класс TVRoomP наследуем от TRoomP, пере­ определив метод определения площади и метод инициализации полей объек­ та (рис. 11.11).

В результате классы будут описаны следующим образом.

Площадь? Г

Комната П

TRoomP

 

 

length, width

L

 

 

Square(), Init(), Print()

с7

 

 

 

Площадь?

Трехмерная

TVRoomP

 

комната П

height

L

_ .^

Square(), Init()

Рис. 11.11. Объекты:

Рис. 11.12. Иерархия

Комната П {а) и

классов для

Трехмерная комната П (б)

TVRoomP

336

//.Иерархии классов

Ва р и а н т 1 - е ошибкой!

Program ex;

Type TRoomP=object

length, width:real;{поля: длина и ширина комнаты}

/unction Square:real; {метод определения площади} procedure Print;{мотод вывода результата на экран} procedure /лОДн'.т^яО;{инициализирующий метод}

end;

 

 

Function TRoomPSquare;

{метод определения площади}

Begin

 

 

Square: = length * width;

End;

 

 

Procedure TRoomPPrint;

{метод вывода результатов}

Begin

 

 

WriteLnCIInouiadb =', Square:6:2); {внутренний вызов метода}

End;

 

 

Procedure TRoomPJnit;

{тело инициализирующего метода}

Begin

 

 

length: ="1;

width: ^^w;

End;

 

 

Type TVRoomP = object(TRoomP)

height:real;

 

{дополнительное поле класса}

function Square:real; {переопределенный метод класса} procedure Init(l,w,h:real); {переопределенный

инициализирующий метод}

end;

Procedure TVRoomPInit; Begin

шЛт/еб//«/7(7,wj;{инициализирует поля базового класса} height: =h; {инициализируем собственное поле класса}

End;

Function TVRoomPSquare;

Begin {обращаемся к методу базового класса}

Square:=inherited Square + 2*height*(length + width); End;

Var A:TRoomP; B:TVRoomP; {объявляем объекты-переменные}

Begin

 

A.Init(3.5,5,1);

{инициализируем поля объекта A}

A.Print;

{выведет «Площадь = 17.85»}

B.Init(3,5,5,1,2.7);

{инициализируем поля объекта В}

B.Print;

{выведет «Площадь = 17.85» - ошибка!!!}

End

 

337

Часть 2. Объектно-ориентированное

программирование

 

Класс TRoomP

 

Класс TVRoomP

 

 

Метод Print

Наследуется

Метод Print

 

I

 

 

 

 

 

Переопределяется

^1

1

1

Метод Square

 

 

Метод Square

 

 

-* - Раннее связывание

мПозднее связывание

Рис, 11.13. Необходимость позднего связывания

Ошибка возникла из-за того, что метод Print, который наследуется клас­ сом TVRoomP, вызывает метод Square. Метод Square в производном классе переопределяется, но метод Print ничего об этом не «знает» и по-прежнему вызывает метод Square класса TRoomP (см. пунктирные стрелки на рис. 11.13).

Для того чтобы метод базового класса мог в зависимости от типа объек­ та, для которого он вызван, обращаться либо к методу базового класса, либо к переопределенному методу производного класса, необходимо тип объекта

определять на этапе выполнения программы (позднее связывание). Пере­ определение методов в этом случае называют слоэюным полиморфизмом, а

соответствующие методы ~ виртуальными полиморфными.

Для организации сложного полиморфизма необходимо:

1)переопределяемые методы описать служебным словом virtual;

2)к методам класса с виртуальными полиморфными методами добавить специальный метод-процедуру - конструктор, в котором служебное слово procedure заменено служебным словом constructor;

3)вызвать конструктор прежде, чем произойдет первое обращение к виртуальным полиморфным методам.

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

Адрес ТВМ хранится в объекте в специальном внутреннем, невидимом для программиста поле размером 2 байта (рис. 11.14). Запись адреса ТВМ в это поле происходит неявно при выполнении конструктора, поэтому попыт­ ки вызовов виртуальных полиморфных методов до выполнения конструкто­ ра приводят к ошибкам нарушения адресации и «зависанию» компьютера.

338

IL Иерархии классов

Программа

Объект

ТВМ класса

 

 

 

 

 

^

Дополнительное

Адреса

»

 

невидимое поле

виртуальных

 

объекта

методов

 

Рис. 11.14. Связь объекта с ТВМ

Как правило, в качестве конструктора используют метод инициализа­ ции полей, так как его обычно вызывают в начале работы с объектом. При повторном вызове конструктора никаких дополнительных действий не вы­ полняется, метод работает как обычная процедура.

Пример 11.5. Продолжение. Исправим ошибку предыдущего варианта программы, объявив метод Square виртуальным полиморфным и используя метод Init в качестве конструктора.

В а р и а н т 2-правильный

Program easel;

Type TRoomP-object

length, width.real;{пояя: длина и ширина комнаты} junction Square:real; virtual; {метод определения площади} procedure Print; {метод вывода результата на экран} constructor Init(lyW:real); {конструктор}

end;

Function TRoomPSquare; {тело метода определения площади} Begin

Square: = length* width; End;

Procedure TRoomPPrint; {тело метода вывода результатов}

Begin

WriteLnf'Площадь =', Square:6:2); {теперь вызов метода

происходит через ТВМ класса }

End;

Constructor TRoomPInit; {тело конструктора} Begin

length: "="1; width: =w;

End;

339

Часть 2. Объектно-ориентированное программирование

Туре TVRoomP = object(TRoomP)

height:real; {дополнительное поле класса}

function Square:real; virtual; {виртуальный полиморфный метод} constructor Init(lyWyh:real); {конструктор}

end;

Constructor TVRoomP.Init; Begin

шЛт/е(^/«//(7,M^^;{инициализируем поля базового класса} height: =h; {инициализируем собстренное поле класса}

End:

Function TVRoomPSquare; Begin

Square:=inherited Square+2'^height*(length+ width); End;

Var A:TRoomP; B:TVRoomP; {объявляем объекты-переменные}

Begin

A.Init(3,5,5J); {конструируем объект A}

A.Print; {выведет «Площадь = 17.85»}

B.Init(3.5,5.],2,7); {конструируем объект В}

B.Print; {выведет «Площадь = 94.64» - верно!!!}

End,

Определены три случая, когда использование позднего связывания обя­ зательно:

1) наследуемый метод для объекта производного класса вызывает метод, переопределенный в производном классе - пример такой ситуации рассмот­ рен выше;

2)объект производного класса через указатель базового класса обраща­ ется к методу, переопределенному производным классом;

3)процедура вызывает переопределенный метод для объекта производ­ ного класса, переданного в процедуру через параметр-переменную, описан­ ный как объект базового класса (данную ситуацию часто называют «проце­ дурой с полиморфным объектом»).

Примечание. При использовании параметров-значений аргумент-объект копируется. При этом копируется объект типа параметра, т.е. базового. Следовательно, передать в проце­ дуру объект производного класса не удастся.

Собственно, все три случая сводятся к одному: обращение к полиморф­ ному методу выполняется через указатель базового класса, которому может быть присвоен адрес объекта не только базового, но и производного класса. В теории программирования объект, адресуемый подобным указателем, по­ лучил название полиморфного,

340