Помогни ни да направим Uroci.net по - богат! Добави урок

C++ част.8 (Обектно-ориентирано)

C++ » C++
fix3d   трудност:    видян: 26521



8.2. Виртуални функции

Виртуалната функция е специална член функция викана чрез указател към публичен базов клас или псевдоним на публичен базов клас; тя се построява динамично по време на изпълнение. Извикваният образец се определя чрез типа на класа на актуалния обект, адресиран от указателя или псевдонима. За потребителя начинът на реализиране на една виртуална функция е явен.

draw(), например, е виртуална функция с образци в ZooAnimal, Bear, Panda, Cat и Leopard. Функция, викаща draw(), може да бъде дефинирана по следния начин:

inline void draw( ZooAnimal& z )
{ z.draw(); }

Ако един аргумент към не член образец на draw() адресира обект от класа Panda, операторът z.draw() ще извика Panda::draw(). Един следващ аргумент, адресиращ обект от класа Cat ще предизвика обръщение към Cat::draw(). Компилаторът решава кои членове функции на класа да извика според на типа на класа на действителния обект. Преди да разгледаме как се декларира и използва виртуална функция, нека накратко видим защо искаме да използваме виртуални функции.

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

За реализирането на множеството се поддържа свързан списък от указатели към животни, за които посетителят ще бъде уведомяван. Когато се натисне клавиша QUIT, главата на свързания списък от ZooAnimal се предава на finalCollage(), която показва животните в подходящ размер и вид на екрана.

Поддържането на свързания лист е просто, тъй като указател към ZooAnimal може да адресира всеки публично извлечен клас. С динамичното построяване не е сложно също да се определи извлечен клас, адресиран от указател към ZooAnimal. finalCollage() може да бъде реализирана по следния начин:

void finalCollage( ZooAnimal *pz )
{for ( ZooAnimal *p = pz; p ; p = p->next )
p->draw();}

В език, в който този проблем не е разрешен по време на изпълнение, остава грижа на програмиста да определи извлечен клас, адресиран от указател към ZooAnimal. Обикновено, това довежда до идентифициране на члена на клас isA() и операторите if-else или switch, които проверяват стойностите на isA(). Без динамично построение finalCollage може да бъде реализирана по следния начин:

// nonobject-oriented implementation
void finalCollage( ZooAnimal *pz )
{ for ( ZooAnimal *p = pz; p ; p = p->next )
switch ( p->isA() )
{ case BEAR:
((Bear *) p)->draw();break;
case PANDA:((Panda *) p)->draw();break;// ... every other derived class} //
switch of isA}

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

След като пандите напуснат зоопарка и си отидат в Китай, типът клас Panda може да се изтрие. Когато пристигнат коали от Австралия, трябва да бъде прибавен тип клас Koala. За всяка промяна в йерархията всеки оператор if-else и switch, който проверява типа на класа, трябва да бъде променен. Програмният код се променя със всяка промяна на йерархията.

Всеки оператор switch увеличава забележимо обема на програмния код. Концептуално прости по своята същност действия се усложняват поради условните тестове, необходими за проверка на типа клас на даден обект. Програмите стават трудни за разчитане.

Потребителите на йерархията и нейната реализация, желаещи да разширят йерархията или да използват приложенията й, трябва да имат достъп до програмния код. Това прави поддържането на системата доста по-трудно и за разпространителя, и за потребителя на системата.

Ако проблемът е разрешен по време на изпълнение, това скрива от потребителя детайлите по реализацията на йерархията на извличането. Условните проверки на типа клас вече не са необходими. Това опростява потребителския код и го прави нуждата от промени по-малка. Потребителският код, който вече не се променя със всяка промяна на йерархията, е по-лесен за програмиране и поддържане.

На свой ред това опростява разширяването на йерархията. Прибавянето на ново извличане от ZooAnimal не изисква промяна в съществуващия код. Главната функция draw() не се интересува от бъдещите извличания от ZooAnimal. Нейният код остава функционален независимо от това в каква степен йерархията е била променена, което означава, че реализацията с изключение на header-файловете може да бъде разпространена в двоичен вид.

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

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

Една виртуална функция се определя като се прибави в началото на декларацията на функцията ключовата дума virtual. Само функции,членове на клас, могат да бъдат декларирани като виртуални. Ключовата дума virtual може да се среща само в тялото на клас. Например,

class Foo {public:virtual bar(); // virtual declaration
};
int Foo::bar() { ... }

Следващата опростена декларация на ZooAnimal декларира четири виртуални функции: debug(), locate(), draw() и isOnDisplay().

#include <stream.h>
class ZooAnimal
{
public:
ZooAnimal( char *whatIs = "ZooAnimal"): isA( whatIs ) {}
void isA()
{ cout << "nt" << isa << "n"; }
void setOpen( int status )
{ isOpen = status; }
virtual isOnDisplay()
{ return isOpen; }
virtual void debug();
virtual void draw() = 0;
protected:virtual void locate() = 0;
char *isa;
char isOpen;
};

void ZooAnimal::debug()
{isA();cout << "tisOpen:"<< ((isOnDisplay()) ? "yes" :
"no") << "n";
}

debug(), locate(), draw() и isOnDisplay() са декларирани като член функции на ZooAnimal защото представляват множество функции, общи за цялата класова йерархия. Те са декларирани като виртуални, защото има детайли на реализацията, които зависят от типа на класа и са първоначално неуточнени. Виртуалната функция на базовия клас служи като място, където се съхраняват все още неопределените типове класове.

Виртуална функция, дефинирана в базовия клас на йерархията, често въобще не се вика, например locate() и draw(). Нито има някакъв смисъл в абстрактен клас като ZooAnimal. Проектантът на класовете може да определи, че една виртуална функция не е дефинирана в абстрактен клас като инициализира с 0 нейната декларация.

virtual void draw() = 0;
virtual void locate() = 0;

draw() и locate() се наричат чисто виртуални функции. Един клас с една или повече чисто виртуални функции може да бъде използван само като базов клас за следващи извличания. Не е правилно създаването на обекти от клас, съдържащ чисто виртуални функции. Например, следващите две дефиниции на ZooAnimal предизвикват грешка по време на компилация:

ZooAnimal *pz = new ZooAnimal; // error
ZooAnimal za; // error

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

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

- ако е осигурена дефиниция, тя служи като образец по премълчаване ,ако извличаният клас не осигури свой образец на виртуалната функция.
- ако е декларирана чисто виртуална функция, извлеченият клас трябва или да дефинира образец на функцията, или да я декларира отново като чисто виртуална функция. Например, Bear трябва или да осигури дефиниции за draw() и locate(), или да ги декларира отново като чисто виртуални функции.

Какво да правим, обаче, ако възнамеряваме да декларираме обекти от типа клас Bear, но все още желаем да отсрочим реализацията на draw() докато не бъдат извлечени отделни видове като Panda или Grizzly ? Не можем да декларираме draw() като чисто виртуална функция и да продължаваме да дефинираме обекти от класа. Ето три алтернативни решения на въпроса:

1. Дефинираме празен образец на виртуална функция: class Bear :
public ZooAnimal {
public:void draw() {}// ...
};

2. Дефинираме образец, обръщението към който предизвиква вътрешна грешка: void Bear::draw()
{ error( INTERNAL, isa, "draw()" ); }

3. Дефинираме образец, който да следи за неочаквано поведение при начертаването на родовия образ на Bear. Тоест, системата продължава работата си, но същевременно се събира информация за изключенията по време на изпълнение, които трябва да бъдат обработени по-нататък.

Извлечен клас може да осигурява свой собствен образец на виртуална функция или по премълчаване да онаследява образеца на базовия клас. На свой ред, той може да въведе своя собствена виртуална функция. Например, Bear предефинира debug(), locate() и draw(); той онаследява образеца на isOnDisplay от ZooAnimal. В допълнение Bear дефинира две нови виртуални функции hibernates() и feedingHours(). Дефиницията на Bear е опростена с цел да се подчертае значението на виртуалните функции.

class Bear :
{ public ZooAnimal
public:Bear( char *whatIs = "Bear" ): ZooAnimal( whatIs ),
feedTime( "2:30" ) {} // intentionally null
void draw(); // replaces ZooAnimal::draw
void locate(); // replaces ZooAnimal::locate
virtual char *feedingHours()
{ return feedime; }
protectted:void debug(); // replaces ZooAnimal::debug
virtual hibernates() { return 1; }
char *feedTime;
};

Предефинирането на една виртуална функция в извлечен клас трябва точно да съответства на името, сигнатурата и типа на връщане на образеца на базовия клас. Ключовата дума virtual не е нужно да бъде специфицирана (макар, че би могла да бъде при желание на потребителя). Дефиницията е като на обикновена член функция.

void Bear::debug()
{isA();cout << "tfeedTime:"
<< feedingHours() << "n";}
void Bear::draw() {/*...code goes here*/}
void Bear::locate() {/*...code goes here*/}

Целият виртуален механизъм всъщност се осъществява от компилатора. Проектантът на класовете само трябва да зададе ключовата дума virtualпри пъървоначалната дефиниция на всеки образец.

Ако повторната декларация в извлечения клас не се съпоставя точно, функцията не се третира като виртуална за извлечения клас. Например, ако Bear декларира debug() по един от следните начини:

void *Bear::debug() {...}// different return type
void Bear::debug( int ) {...}// different signature
debug() няма да бъде виртуална за класа Bear. Например,
Bear b;
ZooAnimal &za = b;
za.debug(); // invoke ZooAnimal::debug()

Един извлечен след това клас от Bear, обаче, все още може да осигурява виртуален образец на debug(), дори Bear да не може. Например,

class Panda : public Bear {
public:
void debug(); // virtual instance
//
...
};

Образецът на Panda е също виртуален поради точното съпоставяне с виртуалната декларация на debug():

Panda p;
ZooAnimal &za = p;
za.debug(); // Panda::debug()

Забележете, че нивата на защита за две от виртуалните функции са различни за образеца на базовия клас и образеца на извлечения клас. Образецът на locate() в ZooAnimal е protected, докато образецът в Bear е public. Аналогично, образецът на debug() в ZooAnimal е public, докато образецът в Bear е protected.

Какви са всъщност нивата на защита на locate() и debug()? Например, целта ни е да напишем такива общи функции като:

void debug( ZooAnimal& z )
{// compiler resolves intended instance
z.debug();}

Тъй като Bear::debug() е protected, верни ли са следните обръщения?

main()
{
// outputs : Bear
// feedTime : 2.30
Bear ursus;
debug( ursus );}

Отговорът е не: debug(ursus) не е вярно. Нивото на достъп на виртуалната функция се определя от типа на класа на указателя или псевдонима, чрез който се извиква член функцията. Всяко виртуално обръщение се третира като 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();}

Аналогично, 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 ); //...
};
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). Следващата дефиниция на класа 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;
};

Базовият клас знае за предефинирането на неговите виртуални функции при следващите извличания на класа. Виканият виртуален образец се определя чрез действителния тип на класа на указател към базовия клас или псевдоним. Базовият клас, обаче не знае за въведените в следващите извличания виртуални функции. Например, не е възможно да се извика hibernate(), въведена от Bear, чрез псевдоним или указател към ZooAnimal. Не е вярно, например, следното:

int hibernate( ZooAnimal &za )
{
// error : hibernate not a member of ZooAnimal
}

Bear е “базов клас” за виртуалната функция hibernate(). Само класовете от неговото ниво в йерархията на онаследяването имат достъп до него. Ако hibernate() е общо действие за широко множество от класове в йерархията на онаследяването, нейната дефиниция не принадлежи на Bear. hibernate() трябва да бъде преместена по-нагоре в йерархията (в този случай се превръща в член на ZooAnimal) или нивата на йерархията трябва да се препроектират. И в двата случая hibernate() трябва да бъде достъпна до всички класове в йерархията, чието общо действие описва.

Таблица 8.1 показва активните виртуални функции в Panda. Втората колона представлява списък на класовете, в които са дефинирани активните функции; третата колона - класът, в който виртуалната функция е първоначално дефинирана.

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

isOnDisplay() ZooAnimal locate()
Panda ZooAnimal draw()
Panda ZooAnimal debug()
Panda ZooAnimal feedingHours()
Bear Bear hibernates()
Bear
Bear adjustPopulation(int)
Endangered
Endangered highlight(short)
Panda Endangered/ Herbivore
Panda
inform(ZooAnimal&)
Herbivore onLoan()
Panda Panda

Таблица 8.1 Виртуални функции на Panda

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

Упражнение 8-1. Сред виртуалните функции, въведени в дефиницията на Bear, има една, която не е на своето място. Коя е тя? Къде трябва да бъде поместена? Защо?
Упражнение 8-2. Параграф 7.4 представя първоначалната дефиниция на ZooAnimal, Bear и Panda. Тези дефиниции са използвани за илюстрация на механизма на извличането и не е необходимо да бъдат пример за най-добрия проект на йерархията ZooAnimal. Предефинирайте дефинициите на класовете, включително множеството от виртуалните функции и образците на X( const X& ) и operator = ( const X& ).
Упражнение 8-3. Проектирайте отново член функцията debug() на йерархията Shapes, описана в Параграф 7.4 да бъде виртуална член функция.
Упражнение 8-4. Реализирайте отново не член функцията debug(), описана в Параграф 7.7 да осъществява виртуална реализация на член функцията 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 cаses go here
}

Определянето на деструкторите в йерархията на извличането като виртуални осигурява извикването на нужния деструктор при прилагането на delete към указател към един клас. Следователно, едно ръководно правило е: деструкторът на един абстрактен клас винаги трябва да се определя като виртуален.

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

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

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

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

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

Най-всобхватния тип на клас, чрез който се извиква виртуална функция е абстрактният базов клас на йерархията на извличането в нашия пример, това е указател или псевдоним от тип ZooAnimal. ZooAnimal има достъп до цялата верига на онаследяването. Това е целта на дефинирането на абстрактен суперклас.

За илюстрация на извикването на виртуални функции нека да направим едно опростено множество от дефиниции на класове. ZooAnimal ще дефинира две виртуални функции - print() и isA() - и образец на виртуален деструктор.

#include <sstrea.h>
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 <string.h>
ZooAnimal::ZooAnimal( char *s ) : next( 0 )
{
name = new char[ strlen(s) + 1 ];
strcpy( name, s );
}

Идеята да се построи смесен лист от извличания от ZooAnimal, свързани с член next. Действителният тип клас на листа не е нужно да бъде известен на програмиста; механизмът на виртуалните функции ще определи типа на клас за всеки елемент.

Функцията 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 <stream.h>
ostream&
operator << ( ostream& os, ZooAnimal& za )
{za.print( os );return os;}

Сега програмистът може да пренасочи всеки член на йерархията на онаследяване ZooAnimal към оператор за изход и има извикана коректната виртуална функция print(). Ще видим един пример за това след като дефинираме типовете класове Bear и Panda. Забележете, че операторът функция не е направен friend за ZooAnimal. Няма необходимост той да бъде обявен като friend тъй като неговият достъп е ограничен с публичния интерфейс на ZooAnimal. Дефиницията на класа Bear изглежда по следния начин:

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 nameZooLocs zooArea;
};

#include <string.h>
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:nt";
os << locTable[ zooArea ] << "n";

}

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

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 <stream.h>
#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 virtuallyn";
pz->ZooAnimal::print( cout );}

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

virtual : ZooAnimal::print()
ZooAnimal name : circus
ZooAnimal 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 <stream.h>
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 <string.h>
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 осигурява, влияят върху представянето му. Те са реализирани по следния начин:

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 <iostream.h>
#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";
};


Страници: «1 2 3 4 5 6 »

Регистрирайте се, за да добавите коментар


Калдейта ЕООД - © 2003-2010. Всички права запазени.
Препоръчваме: Национален Бизнес | Bomba.bg | IT Новини | Диплома.бг | TRAVEL туризъм | Реферати | AmAm.bg | Иде.ли | Курсови работи | Фото Форум | Spodeli.net | Фото-Култ | Atol.bg | Elmaz.com | MobileBulgaria.com | Казанлък.Com