Начало Решаване на проблеми



страница16/19
Дата20.01.2017
Размер4.54 Mb.
#13105
ТипГлава
1   ...   11   12   13   14   15   16   17   18   19
Глава 7: Извличане на класове

Обектно-ориентираното програмиране разширява абстрактните типове

данни като дава възможност те да участват тв отношения тип-подтип.

Това се постига чрез механизъъм, наречен онаследяване. Освен, че

може повторно да реализира общи характеристики, един клас може

да онаследява избрани членове данни и членове функции на други

класове. В С++ онаследяването е реализирано чрез механизма на

извличане на класове, тема на настоящата и следващата глави.

В този параграф отговаряме на следните въпрооси: Защо са

важни отношенията тип-подтип? Какви проблеми по проектирането

са довели програмистите до идеята за разширяване на абстрактните

типове? Какво е истинското значение на термините онаследяване и

обектно-ориентирано програмиране? Да се върнем за илюстрация към

класа Screen, дефиниран в глава 5.

Нашият клас Screen представя една стара технология. Най-

новото поколение работни станции и терминали поддържа концепцията

на прозорците. Прозорецът може да се използва като екран със

следните допълнителни възможности:

* Прозорец може да бъде разширяван или намаляван

* Прозорец може да бъде преместван от една позиция в друга

* Върху един екран могат да бъдат дефинирани много прозорци.

Например терминалът на автора поддържа до седем прозореца. В един

от тях може да бъде въвеждана тази глава от книгата. Във втори се

въвеждат и редактират програмни примери. Тези програми могат да се

компилират и изпълняяват в трети прозорец. В четвърти се чете елек-

тронната поща. Накратко, едновременно прозорците могат да могат

да се използват като самостоятелния екран на стария терминал.

Един прозорец трябва да знае не само размерите си, но и по-

зицията си върху екрана на работната станция. Изискват се кооорди-

нати за локализирането на прозореца и членове функции, които реа-

лизират промяната на неговите размери и преместването му. Прозорецът

ще се нуждае също от членовете данни, дефинирани за класа Screen.

Някои от членовете функции на Screen ще бъдат използвани директно,

други изискват нова реализация.

Прозорецът е вид специализиран екран. Той по-скоро е разширен

подтип на Screen, отколкото независим абстрактен тип данни. На

концептуално ниво Window и Screen представят два елемента на класа

терминален дисплей. На ниво реализация те споделят общи членове

данни и членове функции.

296


------------

Абстрактните типове данни не могат да моделират отношенията тип-

подтип. Най-близката реализация ще бъде да направим Screen член

клас на Window:

class Window {

public:


// ... member functions go here

private:


Screen base;

// ... data members go here

};

Един от проблемите на това представяне е, че членовете данни на



Screen са private и, следователно, не са директно достъпни за

Window. Ако кода за Sccreen е достъпен, Window може да бъде напра-

вен friend за Screen.

За да могат програмистите директно да използват членовете

на Screen, Base трябва да бъде public член на Window:

Window w;

w.base.clear();

Това обаче нарушава скриването на информацията. Програмистът тряб-

ва да познава реализацията на Window - кои членове функции на

Screen са активни в рамките на класа Window и кои са били отново

дефинирани (понеже синтаксиса на обръщенията в двата случая е

различен). home(), например, ще бъде отново дефинирана в класа

Window. Следващите две обръщения извикват различни функции:

w.home();

w.base.home();

Това проектиране предизвиква много програмистки грешки.

За да се осигури скриването на информацията и да се поддържа

еднакъв синтаксис, в Window трябва да се дефинира интерфейсна

функция за всяка активна член функция на Screen. Например, за да

може програмистът да запише

w.clear();

проектантът на класовете трябва да дефинира

inline void Window::clear() { base.clear(); }

Това е скучно, но по принцип е възможно.

Ако обаче искаме да разширяваме още Window, сложността на

реализацията се превръща в сериозно ограничение върху проектирането.

Например, Menu e вид Window. Този клас ще има собствени членове

данни и членове функции. Освен това, Menu ще споделя някои от

членовете на Window и някои членове на Screen, неизползвани

от Window. Онаследяването предлага едно по-добро решение на

проблема.
297

---------------

7.1 Обектно-ориентирано програмиране
Онаследяването позволява членовете на един клас да бъдат изпол-

звани така, сякаш са членове на друг клас. Ако например Window

наследяваше членовете на Screen, всички операции, които потреби-

телят може да извършва с обект от класа Screen, могат също да

бъдат извършвани с обект от класа Window. Не се изисква допълни-

телно програмиране за реализацията на Window с изключение на

операциите, които разширяват или заместват членовете, наследени

от класа Screen.

Първата характеристика на обектно-ориентираното програмиране

е онаследяването. В С++ онаследяването се поддържа чрез механизма

на извличането на класове. Извличането на Window от класа Screen

позволява на Window да наследи членовете на класа Screen. Не-private

членовете на Screen са достъпни за Window така, сякаш са били дек-

ларирани в Window. Например,


Window w;

w.clear.home();


За да бъде извлечен клас, са нужни следните допълнения в син-

таксиса на класа:

1. Към заглавието на класа се включва списък от класове за извличане,

от които ще бъдат наследявани членове. Заглавието на класа

Window изглежда по следния начин:

class Window : public Screen { ... }

Двуетоочието след Window показва, че Window е извлечен от един

или повече предварително дефинирани класове. Ключовата дума

public показва, че Window е публично извлечен клас. Извличанията

могат да бъдат public или private - това влияе върху достъпността

на онаследените членове в рамките на извлечения клас. Името на

класа Screen показва обекта на извличането, наречен базов клас.

Казваме, че Screen е публичен базов клас на Window.

2. Предвидено е и допълнително ниво на достъп - protected. Protected

членовете на един клас се държат като public членове на извлечения

клас , а за останалата част от програмата - като private. За да

бъдат достъпна за членовете функции на Window и функциите, дефини-

рани friend, членовете на Screen трябва да бъдат променени от

private в protected.

Самият извлечен клас също може да бъде обект на извличане. Напри-

мер, Menu може да бъде извлечен от Window:

class Menu : public Window { ... }


298

-----------

На свой ред Menu наследява членове eдновременно от Window и Screen.

Допълнително програмиране се изисква само за тези операции, които

разширяват или заместват наследените членове. Синтаксисът остава

непроменен:


Menu m;

m.display();


Специалните отношения между базовия и извлечения клас се

осъществяват чрез множество предварително дефинирани стандартни

преобразувания. Без явно изписване, извлеченият клас може да

бъде присвоен на всеки един от базовите му класове. Например,


Menu m;

Window &w = m; // ok

Screen *ps = &w; // ok
Това специално отношение между наследените класове поддържа

така наречения родов стил на програмиране, при който действител-

ния тип на класа е неизвестен. Един пример представлява следваща-

та декларация на dumpImage() :


extern void dumpImage( Screen *s );
dumpImage() мооже да бъде извикана с аргумент с тип всеки

един от типовете, за които Screen служи като публичен базов клас,

независимо коолко далеч се намира във веригата от класови извли-

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

ще доведе до грешка по време на компилация. Например,
Screen s;

Window w;

Menu m;

BitVector bv;


// ok : Window is a kind of Screen

dumpImage( &w );


// ok : Menu is a type of Screen

dumpImage( &m );


// ok : argument types match exactly

dumpImage( &s );


// error : BitVector has no relationship with Screen

dumpImagge( &bv );


Всеки от Screen, Window и Menu осигурява по един образец на член

функцията dumpImage(). Целта на нечлен функцията dumpImage() е да

може да приеме като аргумент всеки един от трите типа клас и да

извика съответната функция. В реализация, която не е обектно-ориен-

тирана, основното
299

---------------

усилие на общата dumpImage() е да определи действителния тип

на своя аргумент. Например,


#include "Screen.h"

extern void error( const char*, ...);


enum { SCREEN, WINDOW, MENU };

// non-object oriented code

void dumpImage( Screen *s )

{

// figure out the actual type of s



switch( s->typeOf() )

{

case SCREEN:



s->dumpImage();

break;
case WINDOW:

((Menu *) s)->dumpImage();

break;
case MENU:

((Menu *) s)->dumpImage();

break;
default:

error( "unknown type, s->typeOf() );

break;
}


}
В този случай грижата за определянето на типовете остава за

програмиста. Втората основна характеристика на обектно-ориентираното

програмиране измества тази грижа от програмиста към компилатора. Тази

характеристика се нарича динамично свързване. При динамичното свързва-

не реализацията на dumpImage() е съществено опростена:
void dumpImage( Screen *s )

{
s->dumpImage();


}
При всяко обръщение към dumpImage() автоматично се извиква

съответната член функция dumpImagge(). Това не само намалява слож-

ността и размера на кода, но го прави също разширяем: например,

едно следващо извличане Border може да бъде прибавено към програ-

мата без да има нужда от промени в съществуващия код. Обръщението

към dumpImage()


300

------------

с аргумент от тип Border се осъществява без проблем след новата

компилация. В С++ динамичното свързване се поддържа чрез механиз-

ма на виртуалните членове функции. Член функция се обявява за

виртуална чрез добавянето на ключовата дума virtual в началото

на декларацията на функцията в тялото на класа. Например,
class Screen {

public:


virtual void dumpImage();

// ...


};
Една функция се декларира като виртуална при следните две

условия:
1. Клас ще бъде обект на извличане


2. Реализацията на функцията е типово-зависима
Всеки член образец на dumpImage(), например, е зависим от

представянето на неговия тип на класа - реализацията ще се различава

за Screen, Window и Menu. Проектантът показва зависимостта на реали-

зацията от типовете като декларира dumpImage() да бъде виртуална

член функция.

Отношението тип-подтип между извлечен и базов клас се нарича

йерархия на извличане или йерархия на онаследяване. Извличането от

повече от един базови класове се нарича граф на извличане или онас-

ледяване.

Обектно-ориентираното програмиране се основава на разширената

концепция на абстрактните типове данни, поддържаща дефиницията на

йерархия на онаследяване. Първият език, който осъзна нуждата от

разширяването на абстрактните типове данни и който въведе механизма

на класовете за тази цел, беше Simula. Езикът SmallTalk въведе терми-

на "обектно- ориентиран" за да се подчертае капсулираната природа на

потребителски дефиниран тип клас. В SmallTalk обектно-ориентираното

програмиране превръща скриването на информацията от методология

на проектирането в модел на обектната област.


7.2 Клас Sorted Array
Един масив, който е предназначен предимно за преглед и търсене,

е най-добре да бъде съхраняван нареден. В най-лошия случай за

да бъде открит един елемент в масив е необходимо да бъде претърсен

целия масив. Средно, необходимо е да бъдат прегледани половината

от елементите на масива. Когато броят на елементите в масива е

голям, това може да се окаже твърде скъпо. Чрез използването на

алгоритъма за двоично търсене може да бъде постигнато по-добро

време за откриването на един елемент - ако масивът се съхранява

нареден. И средното, и най-голямото време за двоичното търсене

е log n, където n е броят на елементите във масива. Това е


301

-------------

съществена печалба, осигурена от това, че не се налага постоянно

пренареждане на масива.

Онаследяването освобождава програмиста от необходимостта

отново и изцяло да реализира клас Sorted Array. Ако извлечем

ISortArray от IntArray, ние ще трябва само да реализираме раз-

ликите между двата типа класове. Операциите и данните, които са

същите, ще могат отново да бъдат използвани.

Динамичното свързване освобождава програмиста от нуждата

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

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

личен начин от двата класа, такива като min(), max() и find(),

програмистът не трябва да се грижи за типа на масива, с който

ще оперира. Това позволява на програмиста да включи класа, пред-

ставящ сортиран масив, в съществуващи приложения без да се

грижи за промени в работния код (който обаче трябва да бъде ком-

пилиран отново).

За да се направи една член функция виртуална, декларацията

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

virtual. Членовете данни и всяка от членовете функции, недеклари-

рани като виртуални, ще бъдат използвани от извлечения клас

ISortArray с изключение на конструкторите и оператора за поочленно

присвояване, които не се онаследяват. Промененият клас IntArray

изглежда по следния начин:
const int ArraySize = 24; // default size
class IntArray {

public:


IntArray( int sz = ArraySize );

IntArray( const IntArray& );

virtual ~IntArray() { delete ia; }
IntArray& operator = ( const IntArray& );

int getSize() { return size; };

void grow();
virtual int& operator[] ( int );

virtual void sort( int, int );

virtual int find( int );

virtual int max();

virtual int min();
protected:

// visible to derived classes only

int size;

int *ia;


};
Функцията sort() е разположена във IntArray защото е опе-

рация, приложима към произволен масив. Разликата между IntArray и


302

---------------

извлечения клас е в това, че ISortArray винаги е подреден, нещо

съществено за прилагания алгоритъм на търсене. Търсенето, а не

функцията sort() обусловиха нуждата от извличането.

Член функцията на IntArray sort() е пренос на нечлен

функцията qsort() (виж параграф 3.9 на страница 128 за нейната

дефиниция). Тя не е дадена тук отново, защото нужните промени са

малки и са едно подходящо упражнение за читателя.

Функциите, определени като виртуални, могат, но не е задъл-

жително да бъдат реализирани отново в извлечения клас. Една

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

ново извличане е различна. Извлечен клас, който не реализира

отново дадена виртуална функция, обаче, не се счита за грешен.

Такъв клас просто онаследява виртуалната функция, която не е реа-

лизирал отново. В класа, представящ сортиран масив, се онаследява

виртуалния деструктор на IntArray както и виртуалната функция

sort() (виж параграф 8.2 на страница 348, където се обсъждат

виртуалните деструктори) .

Ето дефиницията на класа ISortArray:


class ISortArray : public IntArray {

public:


ISortArray( int sz = ArraySize )

: IntArray(sz) { dirtyBit = 0; }

int& operator[] ( int );

int find( int );

int min() { checkBit(); return ia[0]; }

int max() { checkBit(); return ia[size-1]; }


private:

void checkBit();

char dirtyBit; // if 1, need resort

};
Конструкторът на базовия клас се извлича преди конструктора

на извлечения клас. Списъкът от членове за инициализация е разши-

рен с аргументи, които се подават на базовия клас. Тъй като

ISortArray не съдържа член данни указател и семантиката му позво-

лява копирането на dirtyBit, подразбиращото се почленно копиране

е достатъчно. Инициализацията или присвояването на един обект оот

ISortArray на друг предизвикват викането на член функцията


IntArray( const IntArray& )
или
operator = ( const IntArray& ).
За дефинирането на виртуалните образци в извличания клас не

се изисква ключовата дума virtual. Обаче, името, типа на връщане

и сигнатурата на всеки образец трябва точно да съответстват на

виртуалния образец, дефиниран в базовия клас.


303

---------------

Ето реализацията на checkBit(). Задачата й е да провери дали

масивът е бил променян. Ако е бил променян, масивът се сортира от-

ново. checkBit() се вика при всяка операция за търсене.
#include "ISort.h"

void ISortArray::checkBit()

{ // has array been possibly modified?

if ( dirtyBit ) {

sort( 0, size-1 );

dirtyBit = 0;


}

}
find() е пренос на binSearch() (виж параграф 3.10 на страница 131

за дефиницията на тази функция). Превръщането й от нечлен функция

в член функция е оставено за упражнение на читателя.

Всичко, което остава, е да реализираме индексния оператор

функция. При всяко негово извикване масивът потенциално е бил

промене и се изисква пренареждането му преди ново търсене. Една

възможна реализация може да изглежда по следния начин:


int& ISortArray::operator[]( int indx )

{
dirtyBit = 1;

return IntArray::operator[]( indx );
}
Една оператор функция може да бъде явно извикана, както

индексния оператор на IntArray. Явнотоо извличане на виртуална

функция с използването на оператор за класов обхват става ста-

тично, а не по време на изпълнение. (Извикването на виртуална

функция чрез обект от клас също става статично).
Упражнение 7-1. Текущата реализация на subscript оператора на

ISortArray при всяко извикване установява dirtyBit. Защо това

е неефективно? Как може да бъде реализирано по-добре?
Упражнение 7-2. Променете ISortArray да пази информация колко

пъти един обект е бил използван преди действителното сортиране.


Упражнение 7-3. Променете ISortArray да пази инфформация колко

пъти един обект е бил сортиран по време на неговото съществува-

не в сравнение с това колко пъти е викана всяка функция за търсе-

не.
304

--------------

Упражнение 7-4. Променете sort() да определя дали един обект

действително се нуждае от соортиране. Пазете информация колко

пъти е бил извикан без необходимост в проценти.


Упражнение 7-5. Извлечете IStatArray от ISortArray за поддържа-

не на статистиката от трите предни упражнения.

305.

7.3 Представяне Zoo Animal


Ще предлоожим един разширен пример за представянето на животните

от една зологическа градина с цел да организираме дискусия върху

извличането на класовете в С++.

Ling-Ling, гигантска панда от Китай, ще бъде преместена за

шест месеца в местната зоологическа градина. Зоологическата гра-

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

Страхувайки се от недостиг на персонал, зоологическата градина

предлага да се инсталират дисплейни терминали в нейния информа-

ционен център, които да отговарят на въпроси за животните, поста-

вяни от посетителите.

Животните съществуват на различни нива на абстракция. Има

отделни екземпляри животни, такива като Ling-Ling. Всяко животно

има вид - например Ling-Ling е гигантска панда. На свой ред видо-

вете са членове на семейства . Гигантската панда е член на се-

мейство мечки. Всяко семейство е на свой ред е член на животинско

царство - в този случай, мъничкото царство на зоологическата гра-

дина.

Класификацията на животните в зоологическата градина чудесно



се представя с йерархия на онаследяването. Фигура 7.1 показва част

от извличането на семейство мечки (Bear). Такъв вид йерархия на

онаследяването е позната като единично онаследяване. В такава йерар-

хия извлеченият клас има само един непосредстено предхождащ го базов

клас.

Класификацията на зоологическата градина не е пълна. Например,



Panda е вид Bear и същевременно е застрашен вид, докато Polar и

Grizzly не са застрашени. Видът Leopard от семейство Cat е също

застрашен.

Застрашен (Endangered) е независима абстракция, която

по случаен начин пресича класификацията. Йерархията на единичното

онаследяване не може да представи застрашената природа на Leopard

и Panda, но това може да направи многократното онаследяване.

Фигура 7.2 показва многократен граф на онаследяването.

Многократното онаследяване дефинира отношение между неза-

висими типове класове. Извлеченият клас може да се разглежда като

изграден от много базови класове. С++ поддържа едновременно еди-

ничното и многократното онаследяване.

Фигура 7.3 показва три нива на извличането ZooAnimal.

Класът ZooAnimal, наречен абстрактен базов клас, е проектиран


306

-----------

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

да бъде разглеждан като незавършен клас, който в някаква степен се

завършва със всяко следващо извличане.

ZooAnimal прави опит да дефинира множество от членове данни и

функции, общи за всички животни от зоологическата градина; извлича-

нията от ZooAnimal правят опит да дефинират специфичното за отделен

клас животни. Следващите извличания са опит това да бъде усъвър-

шенствано още повече. Например, Bear е отделен образец на ZooAnimal,

Panda също е друг специфичен образец.

В процеса на построяване на йерархията на извличането вероятно

проектът на един абстрактен базов клас като ZooAnimal ще бъде невед-

нъж променян и разширяван. Макар че процесът на проектирането за-

почва с абстракцията и завършва с реализацията, пътят, който свързва

тези две точки не е еднопосочен и гладък, а по-скоро е обиколен, с

връщанания назад.

Класът, който служи като коренен клас за йерархията на извли-

чането, се нарича абстрактен суперклас. ZooAnimal е абстрактен

суперклас, понеже служи като коренен базов клас на всички остана-

ли типове класове.
307

---------------

Абстрактният суперклас е централен в проекта на йерархията на

извличането. Независимо от това колко разклонена и сложна става

йерархията, отношенията между класовете се поддържат чрез общото

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

"Застрашен" не е текуща характеристика на всички животни в

зоологическата градина и ,следователно, няма смисъл да извличаме

ZooAnimal от Еndangered или да декларираме обект Endangered да бъде

член данни на ZooAnimal. Endangered служи като помощен абстрактен

базов клас, защото Еndangered е по-скоро независим клас отколкото

подтип на ZooAnimal. ZooAnimal служи като абстрактен суперклас за

животинската таксономия. Многократното онаследяване позволява отдел-

ните типове (такива като Panda) да бъдат свързвани с помощни абстракт-

ни базови типове извън таксономията.

Останалата част от тази глава и глава 8 показват как това

представяне може да бъде реализирано в С++ и разглеждат различни

проблеми на проектирането, коитоо трябва да бъдат разрешени.


Упражнение 7-6. За илюстрация на извличане на класове обикновено

се използват геометричните форми. В най-простия случай формите

са триъгълник, четириъгълник и окръжност. Начертайте йерархията

на онаследяването за тези форми.


Упражнение 7-7. Как квадратът и правоъгълникът се оотнасят към

йерархията на формите? Какво ще кажете за различните видове

триъгълници като правоъгълен триъгълник и равностранния триъгъл-

ник?


7.4 Определяне (спецификация на извличанията)
Следва структурна реализация на част от показаните на фигура 7.3

отношения:


// classes to serve as base classes

class ZooAnimal { ... };

class Endangered { ... };

class Carnivore { ... };

class Herbivore { ... };
// Bear : single derivation

class Bear : public ZooAnimal { ... };


// Cat, Panda : multiple derivations

class Cat : public ZooAnimal, Carnivore { ... };

class Panda : private Endangered, public Bear,

private Herbivore { ... };


308

-------------

Bear, Panda и Cat се наричат извлечени класове. Bear е пример за

еднократно онаследяване. ZooAnimal се нарича публичен базов клас

на Bear. Cat и Panda са примери за многократно онаследяване. Panda

е изграден от един public базов клас (Bear) и два private базови

класове (Endangered и Herbivore). Cat е изграден от един public

базов клас (ZooAnimal) и един private базов клас (Carnivore). Ако

атрибутът public или private не е явно оопределен, по премълчаване

се взима private. Следователно,


class Cat : public ZooAnimal, Carnivore { ... };
декларира, че Carnivore е private базов клас на Cat.

Ling-Ling е обект от класа Panda. Извлечен обект от клас се

декларира по същия начин като неизвлечен обект от клас:
Panda Ling-Ling; // derived class object
От своя страна самият извлечен клас също може да стане базов

клас в едно следващо извличане. Например Bear, извлечен от ZooAnimal,

е публичен базов клас на Panda.

Синтаксисът за дефиниране на базов клас е същият както за

"обикновен" клас със следните две изключения:
1. Членове, които искаме да бъдат онаследени, но да не бъдат публич-

ни, декларираме като protected. В абстрактен тип данни като

BitVector такива членове щяха да бъдат декларирани като private.
2. Членовете функции, чиято реализация зависи от някои детайли в

следващите извличания, неизвестни по време на проектирането на

базовия клас, декларираме като virtual.
Ето една проста, предварителна дефиниция на ZooAnimal:
class ZooAnimal {

public:


ZooAnimal ( char*, char*, short );

virtual ~ZooAnimal();

virtual void draw();

virtual locate();

virtual inform();

protected:

char *name;

char *infoFile;

short location;

short count;

};
309

---------------

Упражнение 7-8. Членовете на абстрактен базов клас представят

общи за цялата йерархия атрибути. Помислете кои ще бъдат общите

членове данни и членове функции в йерархията Shape. Кои функции

ще бъдат декларирани като виртуални?


Упражнение 7-9. Напишете дефиницията на класа Shape според пре-

дишното упражнение. Забележете, че деструкторът на един абстрак-

тен базов клас винаги трябва да бъде деклариран като виртуална

функция.

Дефиниция на извлечен клас
В този параграф ще разгледаме подробно синтаксиса на извличане

на класове. Ето едно опростено, предварително извличане на Bear

от ZooAnimal:
#include "ZooAnimal.h"

class Bear : public ZooAnimal {

public:

Bear( char *, char*, short, char, char);



~Bear();

void locate();

int isOnDisplay();

protected:

char beingFed;

char isDanger;

char onDisplay;

char epoch;

};

Единствената синтактична разлика между дефиницията на обик-



новен клас и извлечен клас е в спецификацията на заглавието:
class Bear : public ZooAnimal
Двуеточието след името на класа показва присъствието на списък от

класове за извличане, съставен от един или повече класове, разде-

лени от запетая. Езикът С++ не поставя ограничение върху броя на

базовите класове, които могат да се съдържат в списъка; но един

клас може да се появи в него само веднъж. Всеки клас от списъка

за извличане трябва вече да е бил дефиниран.

Базовият клас може да бъде public или private. Ако ключовата

дума private или public не е указана, базовият клас по подразбира-

не е private. Например, следващите две заглавия декларират private

извличане на Bear:


// equivalent declarations of a private base class

class Bear : ZooAnimal

class Bear : private ZooAnimal
Предпочитаният метод за деклариране на private базов клас е чрез

явно използване на ключовата дума private. Този стил на програми-

ране се оказва по-малко податлив на грешни интерпретации особено

в случаите на повече базови класове. (Повечето компилатори дават

warning съобщение, ако не е специфицирана ключовата дума public

или private).

При многократно извличане всеки за всеки базов клас трябва

да е специфициран атрибут public или private. По подразбиране

базовият клас не притежава атрибута на предхождащия го базов

клас. В следващото заглавие на клас Endangered може да бъде

сметнат погрешно за публичен базов клас на Panda. Всъщност Endan-

gered е private по подразбиране.


// Endangered by default is private

class Panda : public Bear, Endangered


Атрибутът public или private на базовия клас е валиден за списъка

от класове за извличане, в който присъства. Няма ограничение един

клас да бъде деклариран като public базов клас в един образец и

като private базов клас в друг. Например, макар че ZooAnimal е

public базоов клас на Bear и Cat, няма причина той да не може да

бъде деклариран като private базов клас за Rodent.

Значението на public и private базовите класове и причините

да бъде избран единия или другия се обсъждат в параграф 7.6(стра-

ница 317) поо-късно в тази глава. Следващата част от параграфа

разглежда синтаксиса на достъп до онаследените от базовия клас

членове в рамките на извлечения клас.
Упражнение 7-10. Дефинирайте класа Circle.
Упражнение 7-11. Дефинирайте класа Box.
Упражнение 7-12. Дефинирайте клас Rectangle като извлечен от Box.

Достъп до наследените членове


Bear наследява от ZooAnimal следните членове : name, location,

count, infoFile, locate() и inform(). (Работата с виртуалните

функции draw() и ~ZooAnimal() се обсъжда в глава 8). Достъпът

до наследените членове от ZooAnimal е като до членовете на Bear.

Например,
311

---------------


void objectExample( Bear& ursus ) {

if ( ursus.isOnDisplay() ) {

ursus.locate(HIGH); // const HIGH

if ( ursus.beingFed )

cout << ursus.name << "is now being fed\n";

ursus.inform();

}

}
Макар че достъпът до тях е като до членовете на извлечения клас,



наследените членове поддържат тяхно собствено членство в базовия

клас. Всеки може да бъде достигнат чрез оператора за обхват. Напри-

мер,
if ( ursus.beingFed )

cout << ursus.ZooAnimal::name;

ursus.ZooAnimal::inform();
В повечето случаи използването на оператора за обхват на

класа е излишно. Компилаторът може да намери наследения член

без допълнителна лексична помощ. В два случая, обаче, допълни-

телната помощ е необходима:


1. Когато името на наследения член се използва повторно в извлече-

ния клас.


2. Когато два или повече базови класове дефинират наследяван член

с едно и също име.


Повторното използване на името на наследения член в извлече-

ния клас скрива наследения член. Това е подобно на локален иденти-

фикатор, който повтаря името на променлива, дефинирана в обхвата

на файла. И в двата случая при указването на двойното име се раз-

бира идентификатора, дефиниран в най-близкия обхват.

Bear::locate(int), например, скрива наследения член от ZooAnimal.

Операторът
ursus.locate();
винаги ще се отнася за Bear::locate(int) (и ще довежда до съобщение

грешка "липсващ първи аргумент"). За извличането на скрита наследена

член функция операторът трябва да прочете
ursus.ZooAnimal::locate();
Когато едно име се използва повторно от наследеените членове

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

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

време на компилация. Операторът за обхват на клас трябва да бъде

използван за премахване на двузначността между образците на онасле-

дените членове. Например,

312

-------------


class Endangered { public : highlight ( short ); };

class Herbivore { public : highlight ( short ); };


class Panda : public Bear, public Endangered,

public Herbivore { publicc: locate(); }


// hides ZooAnimal::locate() and Bear::locate(int)

Panda::locate() {

if ( isOnDisplay() ) {

Bear::locate( LOW ); // const LOW

highlight( location ); // error : ambiguous

Endangered::highlight( location ); //ok

}

}
Този метод за явно разрешаване на двузначността има два



съществени недостатъка:
1. Явно извикана виртуална функция се извлича като невиртуална

функция.
2. Двузначноостта се наследява от следващите извличания. Ръковод-

но правило при проектиране на класова йерархия е, че извлеченият

клас не трябва да притежава никакви сведения за реализацията на

класове, разположени преди непосредствения им базов клас в

йерархията. Тук правилоото се нарушава.


Когато наследените членове имат име, същото като това на

всички членове функции, добра стратегия при проектирането е да се

дефинира член функция със същото име. Например,
void Panda::highlight()

{

// encapsulate the ambiguous inherited members



Endangered::highlight( location );

Herbivore::highlight( location );

}
Образецът highlight() от Panda скрива и двата наследени члена и

същевременно осигурява тяхното изпълнение. Двузначността е разре-

шена по начин, явен за потребителя.
Упражнение 7-13. Член функция debug() е полезна на проектанта на

клас. Тя показва текущите стойности на членовете данни на класа.

За всеки клас в йерархията Shapes реализирайте debug() член

функция.
313

---------------
Инициализация на базов клас

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

използва списък от членове за инициализация. Указва се името

на базовия клас, последвано от списък аргументи, ооградени със

скоби (Списъкът от членове за инициализация може да се появи

само в дефиницията, не в декларацията на конструктора). Напри-

мер, ето един списък от членове за инициализация за Bear:
Bear::Bear ( char *nm, char *fil, short loc,

char danger, char age )

: ZooAnimal( nm, fil, loc ),

epoch( age ), isDanger( danger )

{} // deliberately null
В случая на повече от един базови класове за всеки базов клас

се изписва списък. Ето един списък от членоове за инициализация

за Panda:
// PANDA, MIOCENE, CHINA, BAMBOO: enumaration constants

Panda::Panda( char *nm, short loc, char sex )

: Bear( "Ailuropoda melaoleuca",

"Panda", PANDA, MIOCENE ),

Endangered ( CHINA ), Herbivore ( BAMBOO ),

name ( nm ), cell ( loc ), gender ( sex )

{} // deliberately null
В случаите, когато не е дефиниран конструктор за базовия

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

не е необходимо указването му в списък от членове за инициализа-

ция. Базов клас може да бъде инициализиран със списък аргумен-

ти, нужни на конструктора или с друг обект от същия базов тип клас.

Например,


Bear::Bear( ZooAnimal& z )

: ZooAnimal ( z ) { /* ... */ };


Обектът може да бъде също от public извличан тип клас. Например,
Bear::Bear ( const Bear& b )

: ZooAnimal( b ) { /* ... */ };


Следва пример, в който обектът клас няма връзка с базовия

клас, но дефинира оператор за преобразуване, който има такава:


314

-----------


class GiantSloth : public Extincct {

public:


operator Bear() { return b; }

protected:

Bear b;

};
// invokes GiantSloth::operator Bear()



Panda::Panda(GiantSloth& ggs) : Bear(gs) { ... }
// invokes GiantSloth::operator Bear()

Bear::Bear(GiantSloth& gs) : ZooAnimal(gs) { ... };


Конструкторите на базовия клас се викат преди конструктора

на извличания клас. Поради това списъкът от членове за инициали-

зация на извличания клас не може явно да инициализира наследен

член от базовия клас; членовете на базовия клас вече са били

инициализирани. Например, грешка е:
// illegal : explicit initialization of the

// inherited base class member ZooAnimal::name

Bear (char *nm ) : name(nm) /* ... */
Упражнение 7-14. Дефинирайте конструктор(и) на класа Shapes.
Упражнение 7-15. Дефинирайте конструктор(и) на класа Circle.
Упражнение 7-16. Дефинирайте конструктори на класовете Box и

Rectangle.


7.5. Скриване на информацията при извличане

Онаследяването въвежда втора група клиенти на един клас- групата

на проектантите, възнамеряващи да разширят типовете. Членовете

public и private на клас, използвани от групата на потребителите

на класа, са неподходящи за извличане. Protected членовете на

един клас обслужват нуждите на групата на проектантите, същевременно

запазвайки възможността един базов клас да декларира private членове.

Един protected член на клас се явява public за членовете

функции и за friends на извличания клас; той се явява private за

останалата част напрограмата. Извличания клас няма привилигирован

достъп до private членовете на базовия клас. Например, ето едно

опростено извличане от ZooAnimal:
315

---------------


class ZooAnimal {

friend void setPrice (ZooAnimal &);

public:

isOnDisplay();



protected:

char *name;

char onDisplay;

private:


char forSale;

};


class Primate : public ZooAnimal {

friend void canCommunicate (Primate *);

public:

locate();



protected:

short zooArea;

private:

char languageSkills;

};

Извличаният клас наследява всички членове на базовия клас,



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

на базовия клас, които не са private. Primate, например, няма

достъп до члена данни forSale на ZooAnimal, който е private.

Каква е неговата връзка с Primate или произволен друг клас,

извличан от ZooAnimal? Например,
void setPrice( ZooAnimal &z ) {

Primate *pr;

if ( pr->forSale // legal? yes.

&& pr->languagrSkills) // legal? no.

// ...

};
Primate е вид ZooAnimal. setPrice() има същият привилеги-



рован достъъп до членовете на ZooAnimal, наследени от обект от

класа Primate, който има до обект от класа ZooAnimal. forSale

следователно, е достъпен в рамките на setPrice().

Членовете функции и friends на ZooAnimal имат привилегироован

достъп само до членовете на Primate, наследени от ZooAnimal.

Следователно, setPrice() няма достъп до languageSkills.

Ако ZooAnimal се укаже friend за Primate, това прави

достъпни всички членове функции на ZooAnimal за членовете на

Primate, които не са public. Това, обаче, не осигурява достъп

за setPrice(). Eдинственият начин да се осигури досъп на

setPrice() до членовете на primate, които не са public, е явното
316

-----------


й указване като friend за Primate. Тоест, членовете функции и

friends на даден клас се държат по различен начин.

canCommunicate() е friend на Primate и има достъп до него-

вите членове. Ако Orangutan беше извлечен от Primate,

canCommunicate() щеше да има право на достъп до членове на Primate,

които се наследяваха от обект от класа Orangutan. Но има ли право

на достъп canCommunicate() до членовете на ZooAnimal? Например,
void canCommunicate( Primate *pr ) {

ZooAnimal za;

if ( pr->onDisplay // legal? yes.

&& za.onDisplay // legal? no.

&& pr->forSale ) // legal? no.

// ...


}
Общо взето, една friend функция има същите права на достъп

както член-функциите на дадения клас. Членовете функции на Primate

имат достъп до наследените членове от ZooAnimal, които не са

private, но не и до наследените private членове. Следователно,

достъпът на onDisplay е позволен, достъпът на forSale - не.

Извлеченият клас няма специално право на достъп до обекти

от базовия клас. По-скоро извлеченият клас има право на достъп

до наследените не-private членове в обект от базовия клас. Напри-

мер,
Primate::locate() {

ZooAnimal za;

Primate pr;
if ( onDisplay // ok

&& pr.onDisplay // ok

&7 za.onDisplay // errer : no access

// ...


}
В извличането няма приятелство. Primate няма право на

достъп до не-public членове на обект от класа ZooAnimal. Същото

важи и за friends на Primate. Изразът
za.onDisplay
в canCommunicate също не е позволен.

Извличането предполага разширяване на типовете. Primate

е instance of ZooAnimal. Той има характеристиките на ZooAnimal,

но прибавя и множество собствени атрибути. Приятелството пред-

полага даннов достъп до членове, които иначе не са public . То

не дефинира отношения между типове. Извличането не представлява

специален вид приятелство - такова, което
317

---------------


осигурява достъп само до protected, но не и до private членове

на отделни обекти от базовия клас. Например, само friends и

членовете функции на ZooAnimal имат достъп до не-public членове

на обекти от класа ZooAnimal.


Упражнение 7-17. Може ли член функцията debug(), реализирана за

йерархията Shape в параграф 7.4. (стр 312) да бъде направена

private, protected или public член на класа си? Защо?

7.6. Public и Private базови класове


Онаследените членове на public базов клас запазват нивото си

на достъп в рамките на извлечения клас. name и isOnDisplay(),

protected членове на ZooAnimal, се третират като protected чле-

нове на Primate; извличането на Baboon от Primate ще наследи тези

членове. Изобщо, в йерархия на public извличане всеки следващ

извлечен клас има достъп до множество от public и protected чле-

нове на предхождащите го базови класове на дадено ниво на йерар-

хията. Например, public извлечен клас от Panda има достъп до

сбор от public и protected членовете на Bear, ZooAnimal, Endange-

red и Herbivore.

При pivate извличане наследените private и protected членове

стават private членове на извлечения клас. Последиците от това са

следните:
1. Public членовете на базовия клас не могат да бъдат достигнати

през извлечен класов обект.


2. Public и protected членовете на базовия клас са недостъпни за

следващи извличания.

Private извличанията се използват, за да бъде забранен достъ-

па до public интерфейса на базовия клас. Освен това, няма явно

преобразуване на извлечения класов обект в private базов клас.

Параграф 2.5. (стр 71) показва използването на private базов клас-

IntStack използва реализацията на IntArray, но не и неговото мно-

жество public операции. В този случай онаследяването се използва

за поостигане на повторна употреба на кода, а не е метод за дефи-

ниране на отношение тип-подтип.

Ето още един пример. Администрацията на зоологическата гра-

дина иска да проследи популацията на гризачите си, но желае тази

информация да не бъде общодостъпна. Извличането на Rodent от

ZooAnimal се прави private:


class Rodent : private ZooAnimal {

public:


void reportSighting( short loc );

// ...


};
318

-----------


Всички наследени ZooAnimal членове стават private членове

на Rodent. Те могат да бъдат достигнати само от членовете функции

и friends на Rodent. Например, следващото обръщение към locate(),

public член функция на ZooAnimal, е невярно:


Rodent pest;

pest.locate(); // error : ZooAnimal::locate() is sprivate


Rodent не може да бъде присвоен на обект от класа ZooANimal,

защото извличането е private. Ако беше позволено присвояване, нас-

ледените членове нямаше вече да бъдат private. Например, да разгле-

даме следните декларации:


еxtern void locate (ZooAnimal &);

ZooAnimal za;


Следващите две употреби на pest са неверни и предизвикват грешки

по време на компилация:


locate( pest ); // error.

za = pest; // error.

za.locate(); // ok.
Всички следващи извличания от Rodent нямат право на достъп

до член на ZooAnimal, тъй като достъпът е ограничен до public и

protected членовете на базовия клас. Например,
class Rat : public Rodent { ... };

Rat anyRat;


anyRat.reportSighting( CAFETERIA ); // ok

anyRat.locate(); // error : not visible to Rat


Проектантът на класа може да освободи отделни членове на

базовия клас от действието на private извличането. Например,


class Rodent : private ZooAnimal {

public:


// maintain public access level

// do not specify return type or signature

ZooAnimal::inform;

};
pest.locate(); // error : locate() is private

pest.inform(); // ok : inform() is public
Определянето на ZooAnimal::inform в рамките на public частта

на Rodent дава възможност inform() да бъде наследен с неговото

оригинално ниво на достъп. Следващите извличания също ще наследя-

ват inform() като public член:

319

---------------


class Rat : public Rodent { ... };

Rat anyRat;


anyRat.locate(); // error : locate() is not visible

anyRat.inform(); // ok : inform() is a public member


Извлеченият клас само може да запазва нивото на досъп на

наследените членове. Нивото не може да бъде бъде правено по-голямо

или по-малко от оригиналното ниво на достъп, зададено в базовия

клас. Например, не можем да декларираме protected члена на ZooAnimal

onDisplay в publicc частта на Rodent или да декларираме inform()

в protected частта.


7.7. Стандартни преобразувания при извличане


Прилагат се читири предварително дефинирани стандартни преобра-

зувания между извлечения клас и неговия public базов клас:


1. Обект от извлечения клас неявно се преобразува в обект от

публичния базов клас.


2. Псевдоним на извлечения клас неявно се преобразува в псевдо-

ним на публичния базов клас.


3. Указател към извлечения клас неявно се преобразува в указател

към публичния базов клас.


4. Указател към член на базов клас неявно се превръща в ука-

зател към член на публично извлечен клас.


Освен това, указател към произволен класов обект се превръща в

указател към тип void*. (Указател към тип void* изисква явно

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

тип).


Например, ето едно опростено множество дефиниции на кла-

сове и функция f(), която има аргумент псевдоним на обект от

класа ZooAnimal:
class Endangered { ... } endang;

class ZooANimal { ... } zooanim;

class Bear : public ZooAnimal { ... } bear;

class Panda : public Bear { ... } panda;

class ForSale : private ZooAnimal { ... } forsale;
extern void f ( ZooANimal& );
Обекти от класовете ZooAnimal, Bear или Panda могат да викат

f(). В последните два случая се извършва неявно стандартно преоб-

разуване:

320


------------
f( zooanim ); // ok : exact match

f( bear ); // ok : Bear ==> ZooAnimal

f( panda ); // ok : Panda ==> ZooAnimal
И двете следващи обръщиния към f() са неверни. Те предизвикват

грешка по несъответствие на типовете по време на компилация.


f( forsale ); // error : ZooAnimal private

f( endang ); // error : ZooAnimal not a base class


Фиг. 7.4. моделира разбиването на обектите от извлечените класове

според следните опростени дефиниции на ZooAnimal и Bear:


class ZooAnimal {

public:


isA();

// ...


protected:

char *name;

int typeOf;

int someVal;

};
class Bear : public ZooAnimal {

public:


locate();

// ...


protected:

short ZooArea;

int somVal;

};
Bear b, *pb = &b;

ZooAnimal *pz = pb;

На фигура 7.4 е изобразен извлечен клас, съставен от една

(или повече) базови части плюс членовете, уникални за извлечения

клас. И ообект от базов клас, и обект от извлечен клас съдържат

базова част, но само обект от извлечен клас съдържа извлечена

част.


Преобразуването на обект от извлечен клас в обект от базов

клас е безопасно, защото всеки обект от извлечен клас съдържа

обект от базово клас. Обратното преобразуване, обаче, не е безо-

пасно. Затова се изисква явно указване. Например,


// pb and pz both address the Bear object b

// ok : Bear::locate()

pb->locate(); // ok : Bear::locate()
321

---------------


char *name char *name
int typeOf int typeOf част от ZooAnimal
int somVal int somVal
short ZooArea
int somVal част от Bear

фиг. 7.4 Обекти класове при извличане

// error : locate() not a ZooAnimal member

pr->locate();


pb->someVal; // ok : Bear::somVal

pz->somVal; // ok : ZooAnimal::somVal


Освен при обръщение към виртуална функция, необходимо е явно

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

псевдоним или указател към обект от базов клас. Например,
// nonobject-oriented implementation

void locate( ZooAnimal *pZ) {

switch( pZ->isA() ) {

case Bear:

((Bear *) pZ)->locate();

...


case Panda:

((Panda *) pZ)->locate();

...

}
Забележете необхоодимостта от още една двойка скоби около pZ. Опера-



торът
// invoke the ZooAnimal member locate(),

// cast its return value to Bear*

(Bear *)pZ->locate();
се обръща към образеца locate() на ZooAnimal, специфицирайки негоо-

вата стойност на връщане като Bear*.

Присвояването и инициализирането на извлечен клас с неговите

базови класове също изискват явно указване от проограмиста. Например,


322

------------


Bear *pb = new Panda; // ok : implicit conversion

Panda *pp = new Bear; // error : no implicit conversion

Panda *pp = (Panda *)new Bear; // ok
Обект от Panda винаги ще съдържа част на Bear. Обект от Bear може

да съдържа или не обект от Panda взависимост от последното присвоя-

ване. Опасно е указването на базов класоов обект (или указател ) към

неговия извлечен клас. Например, следващата последователност назнача-

ва 24 на неидентифицирано място в паметта.
Bear b;

Bear *pb = new Panda; // ok : implicit conversion

pb = &b; // pb no longer addressed a Panda object

Panda *pp = (Panda *) pb; // oops : no Panda part

pp->ceil = 24; // disaster : no Panda::ceil ...
Указателите към членове на клас се държат по противоположен

начин. Винаги е безопасно да се присвои член на публичен базов

клас на извлечен указател към член на клас. Обаче, не е безопасно

присвояването на член на извлечен клас на базов указател към член

на клас. Това е защото когато се извиква указател към член на клас,

той сочи обект от този клас.

Например, pm_Zoo е указател от ZooAnimal към член на класа,

инициализиран с isA():


int (ZooAnimal::*pm_Zoo)() = ZooAnimal::isA;
При извикването на указателя той се насочва към конкретен обект от

класа от неговия тип:


ZooAnimal z;

(z=*pm_Zoo)();


isA() има достъп до членове на ZooAnimal. Тъй като обест от класа

Bear със сигурност съдържа част на ZooAnimal, безопасно е присвоя-

ването на адреса на isA() на указател от Bear към член на класа:
int (Bear::*pm_Bear)() = ZooANimal::isA;

Bear b;


(b.*pm_Bear)(); // ok
Нека на присвоим pm_Zoo адреса на член функцията на Bear

locate(). Понеже това присвояване не е безопасно, то изисква явно

указване:
pm_Zoo = (int (ZooAnimal::*)()) Bear::locate;
pm_Zoo трябва да се извика да сочи обект отт класа ZooAnimal.

locate(), обаче, има достъп до членове на Bear, които не се съдър-

жат в обект от класа ZooAnimal. Дали извикването на pm_Zoo ще

спъне програмата зависи от действителния тип на обекта от базо-

вия клас. Например,
323

---------------


ZooAnimal *pz = &b; // pz addresses a Bear

(pz->*pm_Zoo)(); // ok


pz = &z; // pz addresses a ZooAnimal

(pz->*pm_Zoo)(); // disaster


Упражнение 7-18. Дадена е следната класова йерархия:
class Node { ... };

class Type : public Node { ... };

class Statement : public Type { ... };

class Identifier : public Expression { ... };

class Function : public Expression { ... };
Кои от следващите инициализации са верни? Обяснете защо?
Упражнение 7-19. Дефинирайте нечлен функция debug(), която да

може да приема всяко извличане от класа Shape. Тя трябва да се

обръща към нужната член функция debug(). (В глава 8 debug() ще

бъде реализирана като виртуална функция)


Упражнение 7-20. Дефинирайте група указатели към членове на

Shape, Circle, Box и Rectangle. Покажете какви присвоявания са

разрешени и кои изискват явно указване.

7.8 Обхват на клас при извличане


Онаследяването осигурява влагане на класовите обхвати. Както е

показано на фигура 7.5 , извлеченият клас може да се разглежда

като затворен н обхвата на базовите си класове.

В рамките на файлов обхват са достъпни всички класове и

external функции; техните обхвати са представени с правоъгълни-

ци, начертани с _ _ _ линия. Всяка член функция е доостъпна

в рамките на своя клас; нейният локален обхват е представен с

правоъгълници, начертани с пунктирана линия. Стрелки свързват

всеки обхват със
324

------------

file scope( :: )

inform


ZooAnimal:: Endangered:: Herbivore::

ZooAnimal:: ZooAnimal:: Endangered:: Herbivore::

inform() isA() inform() inform()

Bear Panda

Bear:: Panda::

inform() inform()


локален обхват файлов обхват

обхват на клас/ затварящ обхват

функция

фигура 7.5 Класов обхват при извличане
325

---------------

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

затварящи обхвати. Всички те се намират на едно и също ниво .

Например Panda трябва да търси на нивото Bear, Endangered и

Herbivore. Фигура 7.5 представя следните дефиниции на класове:


extern inform();
class ZooAnimal {

public:


inform();

isA();


};
class Endangered { public: inform( ZooAnimal* ); }

class Herbivore { public: inform( ZooAnimal* ); }


class Bear : public ZooAnimal

{ public : inform( short ); };


class Panda : public Bear, public Endangered,

public Herbivore

{ public : inform( char* ); };

Появата на isA() в Panda::inform() ще предизвика следното

търсене:
1. Декларирана ли е isA() в рамките на Panda::inform()? Ако да,

стоп. Иначе, следвай стрелката на обхвата. Не е декларирана.


2. Декларирана ли е isA() като член функция на Panda? Ако да,

стоп. Иначе, следвай стрелките на ообхвата на базовите класове.

Не е декларирана.
3. isA() онаследен член ли е от базовите класове Bear, Endangered

и ZooAnimal? Ако да, стоп. Ако е наследен от два или повече

базови класове, има двузначност. Издай съобщение за грешка и

спри. Иначе, някой от базовите класове дали е едновременно и

извлечен от друг? Ако да, следвай стрелката на обхвата към

съответния базов клас; иначе следвай стрелката на обхвата към

файловия обхват. Потвтаряй докато не се намери нужния образец

на функция или докато не се достигне файловия обхват.

ловия обхват. Ако след като е бил достигнат файловия обхват

не може да се намери такъв образец, издай съобщение за грешка

и спри.
При обръщението към isA() в рамките на Panda::inform() е взет

наследения член ZooAnimal::isA().


326

---------

Извличания и презареждане
Наследения член ZooAnimal::locate() има сигнатура, различна от

тази на преименуваната Bear::locate(int). Често начинаещите в

С++ смятат, че сигнатурата е достатъчна за разграничаването на

два образеца- че всъщност с наследените член функции с едно и

също име се работи като с презаредими функции при обръщението

към тях. Например,


// derivation is not overloading

Bear ursus;

ursus.locate(1); // ok : Bear::locate(int)

ursus.locate(); // error : ZooAnimal::locate() is hidden


Обаче извличането не представлява форма на презареждане. Образецът

locate() от ZooAnimal е скрит. Обръщението към ursus.locate() се

разрешава с избора на Bear::locate(int). Затова е издадена грешка.

Намереният образец на locate() изисква аргумент.

Извличането запазва обхвата на базовите класове. Презарежда-

нето изисква презаредимите функции да бъдат дефинирани в рамките

на един и същ обхват. За да се презареди locate(), трябва да бъде

дефиниран още един образец на Bear


inline void Bear::locate() { ZooAnimal::locate(); }
7.9 Извличане, инициализация и присвояване
По премълчаване обект от даден клас, инициализиран с друг обект

от същия клас представлява почленна инициализация(виж параграф

6.2 стр. 247) Същото е в сила и за извлечените обекти на клас.

По реда на тяхното деклариране се прилага почленна инициализа-

ция отначало за базовия клас и след това за всеки извлечен член

на клас.


Ако отделни обекти на клас съдържат членове указатели,въз-

никват проблеми при почленната инициализация. Независимо от това,

че при инициализирането на втори обект не се вземат впредвид

конструкторите, деструкторите се викат два пъти при излизане на

обекта извън обхват. Това може да предизвика опасни отнасяния и

многократни опити за освобождаване на една и съща памет.

За да се разреши този проблем, проектантът на клас трябва

да осигури конструктор за явна инициализация на обект X(const X&).

Всъщност класът следва собствен алгоритъм за почленна инициализа-

ция. Инициализацията на един обект от класа с друг става с извиква-

нето на явния конструктор X(const X&).

Този параграф разглежда връзката между явния конструктор

X(const X&) и извличането. Разглеждат се следните три случая:
1. Когато извлеченият клас не дефинира образец на X(const X&);

но такъв е дефиниран в един или повече базови класове.


2. Когато нито извлеченият клас, нито някой от базовите класове

дефинират образец на X(const X&)


3. Когато и извлеченият клас, и базовите класове дефинират такъв

образец.
Ако извлеченият клас не дефинира образец на X(const X&), винаги

когато обект от извлеченият клас се инициализира с друг обект,

се прилага почленна инициализация. Най-напред се инициализират

почленно базовите класове, като реда на инициализацията следва

реда на декларирането на базовите класове. Конструктор X(const X&)

се вика за всеки базов клас, в който такъв има дефиниран. Напри-

мер, дадено е следното опростено множество от дефиниции на кла-

сове:
class Carnivore { public : Carnivore(); };

class ZooAnimal {

public:

ZooANimal();



ZooAnimal( const ZooAnimal& );

};

class Cat : public ZooAnimal,



public Carnivore { public: Cat(); };
Cat Felix;

Cat Fritz = Felix;


Инициализацията на обекта от клас Cat Fritz с Felix първоначално

ще извика ZooAnimal( const ZooAnimal& ). След това ще се извърши

почленна инициализация на базовия клас Carnivore и членовете на

Cat.


Ако извлеченият клас дефинира образец на X(const X&), той

се вика при всяка инициализация на обект от извлечения клас с

друг обект от класа. Не се инициализира почленно базовата му

част. Отговорността за базовите части пада върху извлечения

X(const X&) образец. Например,
class Bear : public ZooAnimal {

public:


Bear();

Bear( const Bear& );

};
Bear Yogi;

Bear Smokey = Yogi;


Инициализацията на обекта Smokey с обекта Yogi предизвиква

извикването на следната двойка конструктори:


328

-------------


ZooAnimal();

Bear (const Bear&);
Винаги конструктор на базовия клас се вика преди конструктор

на извлечения клас. Не прави изключение и X(const X&). Ако конструк-

торът на базовия клас изисква аргумент, той трябва да бъде осигурен

чрез списък за инициализация.

Тъй като ZooAnimal дефинира свой собствен образец на

X(const X&), за предпочитане е той да бъде извикан. Това може да

бъде направено по следния начин:
Bear::Bear( const Bear& b)

// ZooAnimal( const ZooAnimal& ) invoked

: ZooAnimal( b ) { ... }
Сега инициализирането на Smokey с Yogi предизвиква викането

на следната двойка конструктори:


ZooAnimal( const ZooAnimal& );

Bear( const Bear& );


Ако базовият клас не дефинира собствен образец на X(const X&), се

прилага рекурсивно почленна инициализация.

По подобен начин почленната инициализация може да бъде пре-

небрегната с явното осигуряване на образец на оператора

operator= (const X&). Използва се паралелно с конструктора

X(const X&).

Ако в извлечения клас е дефиниран пример на

operator = (const X&), при инициализирането на обект от един клас

с обект от същия клас ще бъде извлечен той. Базовият клас и обект,

който е член клас, носят отговорността за оператора за присвояване

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

Дадено е следното присвояване на извлечен обект на класа

Bear на обект от публичния базов клас ZooAnimal, кой образец на

оператора за присвояване ще бъде извикан? И ZooAnimal, и Bear

дефинират оператор за присвояване operator = (const X&).
Bear ursus;

ZooAnimal onLoan;


onLoan = ursus;
Типа на класа на левия операнд при присвояването определя типа

на класа на присвояването. Базовата част на ZooAnimal в Bear

обекта ursus ще бъде присвоена на onLoan. Следователно, като

че ли става присвояване на два обекта от ZooAnimal и извлича-

не на оператора за присвояване от ZooAnimal. Същото е валидно

и при инициализация на обекти:


Bear Fozzie;

ZooAnimal On_Loan = Fozzie;


също извиква конструктора за инициализация на обекти от ZooAnimal,

а не от Bear.

Изобщо, ако един клас осигурява един от явните образци на член

за инициализация или присвояване, той трябва да осигурява също и

другия. Явните образци обикновено са необходими в следните случаи:
1. Семантиката на представянето на класа прави невалидна някаква

стойност при копиране на един или повече членове. Членът index

на класа String, например, при всяко копиране трябва да бъде

установяван на 0.


2. Класът съдържа членове указатели и деструктор. Копирането

изисква да се пази reference броя или допълнително да се отделя

копира памет.
Упражнение 7-21. Дефинирайте X( const X& ) и operator = (const X&)

за класа Shape, невалидни за Circle. Кои са извлечените конструк-

тори за следващите два обекта от тип Circle?
Circle c1;

Circle c2 = c1;


Упражнение 7-22. Дефинирайте X(const &X) и operator = (const X&)

за класа Box, невалидни за класа Rectangle. Кои са извлечените

конструктори за следващите обекти класове?

331


-----------

Каталог: files -> tu files
tu files -> Увод в компютърната графика
tu files -> Xii. Защита и безопасност на ос
tu files -> Електрически апарати
tu files -> Средства за описание на синтаксиса
tu files -> Stratofortress
tu files -> Писане на скриптове за bash шел : версия 2
tu files -> 6Технологии на компютърната графика 1Модели на изображението
tu files -> Z=f(x), където x- входни данни; z
tu files -> Body name библиотека global Matrix imports (достъп по име) … var m[N, N] := … end decl., proc … resource f final code imports node, Matrix end name var x: node node; if x … Matrix m[3,4] :=: … end


Сподели с приятели:
1   ...   11   12   13   14   15   16   17   18   19




©obuch.info 2024
отнасят до администрацията

    Начална страница