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



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

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

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

третира като public, понеже debug() е public член на ZooAnimal. Забе-

лежете, че и този образец, и следващия предполагат, че ZooAnimal не

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

main()
{

ZooAnimal *pz = new Bear;

pz->debug(); // invokes Bear::debug()


pz = new ZooAnimal;

pz->setOpen(1); // open the zoo

pz->debug(); // invokes ZooAnimal::debug()

}
след компилация изходът е:


Bear

feedTime: 2:30


ZooAnimal

isOpen: yes

Извикванията на виртуалните образци на debug() чрез типа клас Bear,

обаче, могат да бъдат третирани като имащи достъп protected. След-

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

protected член на клас:

main()

{

ZooAnimal *pz = new Bear;



pz->debug(); // invokes Bear::debug()
Bear *pb = (Bear *) new ZooAnimal; // dangerous
// illegal : main has no access privilege

// to the protected members of Bear!

pb->debug();

}
345

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

Аналогично, locate() e protected член на ZooAnimal и същевре-

менно е public член на Bear. Виртуалните извиквания чрез указател

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

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

обаче, ще бъдат третирани като имащи protected достъп. Следващото

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

докато нечлен функцията locate() не се направи friend за ZooAnimal.


void locate( ZooAnimal *pz )

{

// locate() has no access privilege;



// unless made friend to ZooAnimal

pz->locate(); // error

}

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



виртуалните функции се разрешават по време на изпълнение, за тях

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

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

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

ва обръщението.

Bear осигурява свои собствени образци на три от четирите вир-

туални функции, които дефинира ZooAnimal; допълнително той също де-

финира две виртуални функции. Извличане на Panda наследява шестте

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

Коя от трите виртуални функции на ZooAnimal е предефинирана в

рамките на Bear? Те остават членове, наследени от ZooAnimal, и отно-

во могат явно да бъдат викани. Например, debug() може да бъде отново

реализирана по следния начин:

void Bear::debug()

{

ZooAnimal::debug();



cout << "Fed time: "

<< feedTime << "n";

}

Panda се извлича от три базови класа: Bear, Endangered и



Herbivore. И Endangered, и Herbivore дефинират две виртуални

функции:
class Endangered {

public:

virtual adjustPopulation( int );



virtual highlight( short );

// ...


};

346


----------
class Herbivore {

public:


virtual inform( ZooAnimal& );

virtual highlight( short );

// ...

};

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



базови класал Тя може да осигурява свои собствени образци за всяка

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

виртуални функции.

Panda онаследява две виртуални функции, наречени highlight() -

една при извличането от Endangered и една при извличането от Herbivore.

Това е проблематично само ако highlight() се извлича от тип клас Panda

или ако типът клас Panda е обект на извличане. И в двата случая изпол-

зването на псевдоним на highlight() е двузначно. Например,


RedPanda : public Panda { ... };

RedPanda pandy;

Panda &rp = pandy;
// ...

rp.highlight(); // error : ambiguous


За да се избегне евентуална двузначност, Panda дефинира соб-

ствен образец на highlight() (виж Параграф 7.4 на стр. 311, където

се обсъжда проблема). Слезващата дефиниция на класа Panda е опросте-

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

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

class Panda : public Bear, public Endangered,

public Herbivore {

public:

Panda( char *whatIs = "Panda: ) :



Bear( whatIs ) {}

virtual onLoan();

inform( ZooAnimal& );

void draw();

int debug();

void locate();

hibernates() { return 0; }

protected:

highlight( short );

short cell;

short onLoan;

};

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



функции при следващите извличания на класа. Виканият виртуален

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

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

347


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

Например, не е възможно да се извика hibernate(), въведена от

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

ример, следното:

int hibernate( ZooAnimal &za )

{

// error : hibernate not a member of ZooAnimal



return za.hibernate();

}


Bear е "базов клас" за виртуалната функция hibernate(). Само

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

достъп до него. Ако hibernate() е общо действие за широко мно-

жество от класове в йерархията на онаследяването, нейната дефи-

ниция не принадлежи на Bear. hibernate() трябва да бъде преместена

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

ZooAnimal) или нивата на йерархията трябва да се препроектират.

И в двата случая hibernate() трябва да бъде достъпна до всички

класове в йерархията, чието общо действие описва.

Таблица 8.1 показва активните виртуални функции в Panda. Вто-

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

рани активните функции; третата колона - класът, в който вир-

туалната функция е първоначално дефинирана.

-ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЄЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЄЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ¦

L Виртуална функция L Активна функция L Първа дефиниция L

єЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇїЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇїЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇL

L isOnDisplay() L ZooAnimal L ZooAnimal L

L locate() L Panda L ZooAnimal L

L draw() L Panda L ZooAnimal L

L debug() L Panda L ZooAnimal L

L feedingHours() L Bear L Bear L

L hibernates() L Bear L Bear L

L adjustPopulation(int)L Endangered L Endangered L

L highlight(short) L Panda L Endangered/ L

L L L Herbivore L

L inform(ZooAnimal&) L Panda L Herbivore L

L onLoan() L Panda L Panda L

L L L L


ЁЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇёЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇёЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ-
Таблица 8.1 Виртуални функции на Panda

Трудно е да се направи добро проектиране на йерархия на онас-

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

повторения. Проектирането на йерархии е област на активни изследва-

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

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

348

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


Упражнение 8-1. Сред виртуалните функции, въведени в дефиницията на

Bear, има една, която не е на своето място. Коя е тя? Къде трябва

да бъде поместена? Защо?

Упражнение 8-2. Параграф 7.4 на стр.308 представя първоначалната

дефиниция на ZooAnimal, Bear и Panda. Тези дефиниции са използвани

за илюстрация на механизма на извличането и не е необходимо да

бъдат пример за най-добрия проект на йерархията ZooAnimal. Пре-

дефинирайте дефинициите на класовете, включително множеството от

виртуалните функции и образците на X( const X& ) и

operator = ( const X& ).

Упражнение 8-3. Проектирайте отново член функцията debug() на

йерархията Shapes, описана в Параграф 7л4 на стр.312 да бъде

виртуална член функция.

Упражнение 8-4. Реализирайте отново нечлен функцията debug(), опи-

сана в Параграф 7.7 на стр.323 да осъществява виртуална реализа-

ция на член функцията debug() на йерарйията Shapes.

Упражнение 8-5. Реализирайте виртуалната функция draw() за йерар-

хията Shapes.

Упражнение 8-6. Реализирайте виртуалната функция reSize() за

йерархията Shapes.


Виртуални деструктори


Свързаният списък от елементи на ZooAnimal, подаден на finalCollage(),

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

Обикновено се добавя for-loop от следния вид в края на finalCollage()

за освобождаването на памет:

for ( ZooAnimal *p = pz->next; p;

pz = p, p = p->next )

delete pz;

За нещастие тази стратегия не върши работа. Явното изтриване на pz

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

от pz. Обаче обектът може да не е ZooAnimal, а някакъв след това

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

действителния тип на класа, сочен от указателя. Но всяко явно извик-

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

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


for ( ZooAnimal *p = pz->next; p;

pz = p, p = p->next )

switch( pz->isA() )

{

case BEAR:



// direct invocation of destructor

((Bear*)pz)->Bear::~Bear();

break;
case PANDA:

// indirect invocation through delete

delete (Panda *) pz;

break;
// ... more ceses go here

}

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



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

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

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

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


Упражнение 8-7. Дефинирайте деструкторите на йерархията Shapes

като виртуални.


Извикване на виртуални функции


Виртуална функция се вика чрез указател към даден тип клас или

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

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

от:


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

* Тези образци, които са предефинирани от следващи извличания.


Образецът на виртуалната функция, който се изпълнява при всяко

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

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

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

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

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

ZooAnimal има достъп до цялата верига на онаследяването. Това е

целта на дефинирането на абстрактен суперклас.

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

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

ще дефинира две виртуални функции - print() и isA() - и образец

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

enum ZooLocs { ZOOANIMAL, BEAR, PANDA };


class ZooAnimal {

public:


ZooAnimal( char *s = "ZooAnimal" );

virtual ~ZooAnimal() { delete name; }

void link( ZooAnimal* );

virtual void print( ostream& );

virtual void isA( ostream& );

protected:

char *name;

ZooAnimal *next;

};
#include

ZooAnimal::ZooAnimal( char *s ) : next( 0 )

{

name = new char[ strlen(s) + 1 ];



strcpy( name, s );

}

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



свързани с член next. Действителният тип клас на листа не е нужно

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

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

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


Функцията link() на ZooAnimal приема аргумент от тип ZooAnimal*

и го присвоява на next. Това е реализирано по следния начин:


void ZooAnimal::link( ZooAnimal *za )

{

za->next = next;



next = za;

}


isA() и print() са реализирани като виртуални функции. Всяко

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

isA() съобщава своя тип клас; print() усъвършенства представянето

на типа клас. Всяка една от двете функции приема като аргумент

псевдоним на ostream. Ето реализацията:
void ZooAnimal::isA( ostream& os )

{

os << "ZooAnimal name : "



<< name << "\n";
}
void ZooAnimal::print( osream& os )

{

isA( os ); // virtual invocation



}

Една от целите на нашия проект е поддържането вход-изход за

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

За да се постигне това, трябва да презаредим оператора за изход да

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

по следния начин:

#include
ostream&

operator << ( ostream& os, ZooAnimal& za )

{

za.print( os );



return os;

}

Сега програмистът може да пренасочи всеки член на йерархията



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

ректната виртуална функция print(). Ще видим един пример за това след

като дефинираме типовете класове Bear и Panda. Забележете, че опера-

торът функция не е направен friend за ZooAnimal. Няма необходимост

той да бъде обявен като friend тъй като неговият достъп е ограничен

с публичния интерфейс на ZooAnimal.

Дефиницията на класа Bear изглежда по следния начин:

352


-----------
class Bear : public ZooAnimal {

public:


Bear( char *s = "Bear", ZooLocs loc = BEAR,

char *sci = "Ursidae" );

~Bear() { delete sciName; }

void print( ostream& );

void isA( ostream& );

protected:

char *sciName; // scientific name

ZooLocs zooArea;

};
#include

Bear::Bear( char *s, ZooLocs loc, char *sci )

: ZooAnimal( s ), zooArea( loc ) {

sciName = new char[ strlen(sci) + 1 ];

strcpy( sciName, sci );

}


Bear въвежда два допълнителни члена данни:
* sciName, научното наименование на животното.
* zooArea, мястото в зоопарка, обитавано от животното.
Виртуалните образци на isA() и print() на Bear дават отраже-

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


void Bear::isA( ostream& os ) {

ZooAnimal::isA( os ); // static invocation

os << "\tscientific name:\t";

os << sciName << "\n";

}
static char *locTable[] = {

"The entire animal display area", // ZOOANIMAL

"NorthWest : B1 : area Brown", // BEAR

"NorthWest : B1.P : area BrownSpots" // PANDA

// ... and so on

};
void Bear::print( ostream& os ) {

ZooAnimal::print( os ); // static invocation

os << "\tZoo Area Location:\n\t";

os << locTable[ zooArea ] << "\n";

}

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



функция статично по време на компилиране:

353


---------------
1. Когато виртуалната функция се вика чрез обект от типа клас. В

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

чрез обекта za от тип ZooAnimal и това става статично. А вика-

нето на isA() чрез указател pz към обект от типа ZooAnimal се

разглежда като виртуално обръщение.
#include "ZooAnimal.h"
main() {

ZooAnimal za;

ZooANimal *pz;
// ...
za.isA( cout ); // nonvirtual invocation

(*pz).isA( cout ); // virtual invocation

}
2. Когато виртуалната функция се вика явно чрез указател или

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

Например,
#include

#include "Bear.h"

#include "ZooAnimal.h"
main() {

Bear yogi ( "cartoon Bear", BEAR,

"ursus cartoons" );

ZooAnimal circus( "circusZooAnimal" );

ZooAnimal *pz;
pz = &circus;

cout << "virtual : ZooAnimal::print()\n";

pz->print( cout );
pz = &yogi;

cout << "\nvirtual : Bear::print()\n";

pz->print( cout );
cout << "\nnonvirtual : ZooAnimal::print()\n";

cout << "note : isA() is invoked virtually\n";

pz->ZooAnimal::print( cout );

}
При компилация и изпълнение се получава следното:


354


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

virtual : ZooAnimal::print()

ZooAnimal name : circusZooAnimal
virtual : Bear::print()

ZooAnimal name : cartoon Bear

scientific name : ursus cartoonus

Zoo Area Location:

NorthWest : B1 : area Brown
nonvirtual : ZooAnimal::print()

note: isA() is invoked virtually

ZooAnimal name: cartoon Bear

scientific name: ursus cartoonus


3. Когато виртуалната функция се вика в рамките на конструктора

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

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

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

е изтрит.


Panda въвежда два допълнителни члена данни: indName, името

на отделното животно и cell, клетката, в която живее то. Ето дефи-

ницията на Panda:


#include
class Panda : public Bear {

public:


Panda( char *nm, int room, char *s = "Panda",

char *sci = "Ailuropoda Melaoleuca",

ZooLocs loc = PANDA );

~Panda() { delete indName; }

void print( ostream& );

void isA( ostream& );

protected:

char *indName; // name of individual animal

int cell;

};
#include

Panda::Panda( char *nm, int room, char *s,

char *sci, ZooLocs loc )

: Bear( s, loc, sci ), cell( room ) {

indName = new char [strlen(nm) + 1];

strcpy( indName, nm );

}


Виртуалните образци на isA() и print(), които Panda осигурява,

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


355


---------------
void Panda::isA( ostream& os )

{

Bear::isA( os );



os << "\twe call our friend:\t";

os << indName << "\n";

}
void Panda::print( os )

{

Bear::print( os );



os << "\tRoom Location:\t";

os << cell << "\n";

}

Сега да приложим всичко това в няколко примера. Първият ни



пример показва виртуално извикване на print() чрез псевдоним на

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

зец на оператора за изход ("<<"). Всяко обръщение към
za.print( os );
в рамките на образец на оператора за изход извиква виртуалния обра-

зец, дефиниран чрез действителния тип клас на za.


#include

#include "ZooANimal.h"

#include "Bear.h"

#include "Panda.h"
ZooANimal circus( "circusZooAnimal");

Bear yogi("cartoon Bear",BEAR,"ursus cartoonsus");

Panda yinYang("Yin Yang",1001,"Giant Panda");
main() {

cout << "Invokation by a ZooAnimal object:\n"



<< circus << "\n";
cout << "\nInvokation by a Bear object:\n"

<< yogi << "\n";
cout << "\nInvokation by a Panda object:\n"

<< yinYang << "\n";

};

При компилация и изпълнение се получава следното:


356


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

Invokation by a ZooAnimal object:

ZooAnimal name: circusZooAnimal
Invokation by a Bear object:

ZooAnimal name: cartoon Bear

scientific name: ursus cartoonus

Zoo Area Location:

NorthWest: B1: area BroWn
Invokation by a Panda object:

ZooAnimal name: Giant Panda

scientific name: Ailuropoda Melaoleuca

we call our friend: Yin Yang

Zoo Area Location:

NorthWest: B1.P: area BrownSpots

Room Location: 1001

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

обекти. Той вика виртуалната функция isA().

#include

#include "ZooANimal.h"

#include "Bear.h"

#include "Panda.h"
ZooAnimal circus( "circusZooAnimal" );

Bear yogi("cartoon Bear", BEAR, "ursus cartoonus");

Panda yinYang("Yin Yang", 1001, "Giant Panda");
main() {

ZooAnimal *pz;


pz = &circus;

cout << "virtual: ZooAnimal::isA():\n";

pz->isA( cout );
pz = &yogi;

cout << "\nvirtual: Bear::isA():\n";

pz->isA( cout );
pz = &yinYang;

cout << "\nvirtual: Panda::isA():\n";

pz->isA( cout );

}


357

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


При компилация и изпълнение се получава следното:

virtual: ZooAnimal::isA():

ZooAnimal name: circusZooAnimal
virtual: Bear::isA():

ZooAnimal name: cartoon Bear

scientific name: ursus cartoonus
virtual: Panda::isA():

ZooAnimal name: Giant Panda

scientific name: Ailuropoda Melaoleuca

we call our friend: Yin Yang


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

pz се разпознава по време на компилация. Нека оставим виртуалния ме-

ханизъм и да извикаме всяка функция статично:


#include

#include "ZooAnimal.h"

#include "Bear.h"

#include "Panda.h"


ZooAnimal circus( "circusZooAnimal");

Bear yogi("cartoon Bear", BEAR, "ursus cartoonus");

Panda yinYang("Yin Yang", 1001, "Giant Panda");
main() {

ZooAnimal *pz = &yinYang;

cout << "Nonvirtual invokation of Panda::isA():\n";

((Panda*)pz) ->Panda::isA( cout );


pz = &yogi;

cout << "\nNonvirtual invokation of Bear::isA():\n";

((Bear*)pz) ->Bear::isA( cout );

}

Невиртуалното извикване на Panda::isA() чрез указател към



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

следващите извличания от класа Panda; това е част от виртуалния

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

ние се получава следното:


358


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

Nonvirtual invocation of Panda::isA():

ZooAnimal name: Giant Panda

scientific name: Ailoropoda Melaoleuca

we call our friend: Yin Yang
Nonvirtual invocation of Bear::isA():

ZooAnimal name: cartoon Bear

scientific name: ursuss cartoonus

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

към ZooAnimal. Той е реализиран с използването на следната нечлен

функция print() - print() трябва да има достъп до членовете данни

на ZooAnimal, които не са public, затова трябва да бъде обявена

като friend за ZooAnimal.

#include

#include "ZooAnimal.h"


void print( ZooAnimal *pz, ostream &os = cout )

{

while( pz ) {



pz->print( os );

os << "\n";

pz = pz->next;


Каталог: 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
отнасят до администрацията

    Начална страница