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



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

}


За нашия програмен пример имаме нужда от указател към ZooAnimal,

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


ZooAnimal *headPptr = 0;

main() дефинираме по следния начин:

#include

#include "ZooAnimal.h"


extern ZooAnimal *makeList( ZooAnimal* );

ZooAnimal *headPtr = 0;


main() {

cout << "A Program to Illustrate Virtual Functions\n";

headPtr = makeList( headPtr );

print( headPtr );

}

makeList(), на свой ред, е дефинирана по следния начин:


359


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

#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");

Panda rocky("Rocky",943,"Red Panda","Ailurus fulgens");


ZooAnimal *makeList( ZooAnimal *ptr )

{

// for simplicity, hand code list



ptr = &yinYang;

ptr->link( &circus );

ptr->link( &yogi );

ptr->link( &rocky );

return ptr;

}


При компилация и изпълнение се получава следното:
A Program to Illustrate Virtual Functions

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
ZooAnimal name: Red Panda

scientific name: Ailurus fulgens

we call our friend: Rocky

Zoo Area Location:

NorthWest: B1.P: area BrownSpots

Room Location: 943


ZooAnimal name: cartoon Bear

scientific name: ursus cartoons

Zoo Area Location:

NorthWest: B1: area Brown


ZooAnimal name: circusZooAnimal

С изключение на makeList(), програмата не взема под внимание детайлите

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

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

обектно-ориентираното програмиране.

360


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

от тип Shape*. Тя трябва да чертае окръжност, правоъгълен триъгълник и

правоъгълник.

Упражнение 8-9. Реализирайте нечлен функцията reSize(), която има аргумент

от тип Shape& (ще бъде нужен също аргумент за размер). Приложете после-

дователно draw(), reSize() и след това draw() за окръжност, равностранен

триъгълник и квадрат.

Упражнение 8-10. Прибавете виртуален образец на draw(), която пише в

обект от клас Screen.

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

обект от йерархията Shape на ostream и restore(), която чете изхода от

save().

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

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

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

на членовоте.

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

продължаващи повече от сто години, относно това дали Panda принадлежи

на фамилията Raccoon или на фамилията Bear. От компютърна гледна точка,

най-доброто решение е Panda да се извлича и от двете фамилии.

class Panda: public Bear, public Raccoon { ... }

Panda онаследява базовия клас ZooAnimal едновременно от Bear и от

Raccoon; в Panda има две части за базовия клас. Декларирането на

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

ZooAnimal; в следния ред:

361


---------------
ZooAnimal(); // base class of Bear

Bear(); // first Panda base class

ZooAnimal(); // base class of Raccoon

Raccoon(); // second Panda base class

Panda(); // derived class constructor is always last

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

дефиниция на ZooAnimal:

class ZooAnimal { // simplified definition

public:

void locate();



protected:

short zooArea;

};
class Bear : public ZooAnimal { /* ... */ }

class Raccoon : public ZooAnimal { /* ... */ }


Panda съдържа две множества от членове данни на ZooAnimal: eдин член

данни zooArea, наследен чрез Raccoon, и един, наследен чрез Bear.

Фигура 8.1 показва това:

-ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ¦ -ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ¦

L L L L


L ZooAnimal part L L ZooAnimal part L

L L L L


єЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇL єЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇL

L L L L


L Bear part L L Bear part L

L L L L


L L L L

ЁЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ- єЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇL

L L

class Bear:public ZooAnimal L ZooAnimal part L



L L

L L


-ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ¦ єЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇL

L L L L


L ZooAnimal part L L Raccoon part L

L L L L


L L L L

єЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇL єЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇL

L L L L

L Raccoon part L L Panda part L



L L L L

L L L L


L L L L

ЁЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ- ЁЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ-


class Raccoon: public ZooAnimal class Panda: public Bear,

public Raccoon


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


363


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

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

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

Освен това, се разхищава памет заради пространството, в което се съхра-

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

Panda се нуждае само от един образец на базовия клас ZooAnimal.

Концептуално, Panda може да се разглежда като един насочен граф без

цикли със споделен образец на базовия клас ZooAnimal. По подразбиране,

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

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

базов клас. (виж Фигура 8.2 )

Необходимо е да се намери начин подразбиращият се механизъм на

онаследяването да се пренебрегва. Трябва да бъде възможно да се

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

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

случай част от потребителите (възможно е твърде малка) ще бъде затруд-

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

Методът за пренебрегване на подразбиращия се механизъм на онас-

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

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

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

на извличането, генерира се само един негов образец. Например, Panda

ще съдържа само един образец на споделения базов клас ZooAniaml.

Достъпът до членовте на класа вече не е двузначен.


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


Един базов клас се определя като виртуален чрез добавянето на клю-

човата дума virtual към неговата декларация. Например, следващата

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

// placement order of keywords public

// and virtual is not significant

class Bear : public virtual ZooAnimal { /* ... */ }

class Raccoon : virtual public ZooAnimal { /* ... */ }

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

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

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

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

нужда да бъде променяна дефиницията на ZooAnimal, за да се определи

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

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


ЇЇЇЇЇЇЇЇЇЇЇЇЇЇ

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

AT&T 2.0 версията на С++ го въвежда. Според сегашните правила, ако

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

предизиква грешка по време на компилация. По-ранното ограничение

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

ция.

364

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


ZooAnimal

Bear Conceptual Picture Raccoon

of Panda
Panda

ZooAnimal ZooAnimal


Ordinary Tree

Implementation


Bear Raccoon

Panda


Фигура 8.2 Невиртуално многократно онаследяване

365


---------------
class ZooAnimal { // simplified definition

public:


ZooAnimal() { zooArea = 0; name = 0; }

ZooAnimal( char*, short );

void locate();

protected:

short zooArea;

char *name;

};

По същия начин се използват обектите от класовете Bear и Raccoon. Един



виртуален базов клас се инициализира както невиртуален базов клас:

Bear::Bear( char *nm )

: ZooAnimal( nm, BEAR ) { ... }
Raccoon::Raccoon( char *nm )

: ZooAnimal( nm, RACCOON ) { ... }


Декларацията на Panda изглежда по същия начин както нейния

невиртуален образец:

class Panda : public Bear, public Raccoon { ... };


Тъй като и Bear, и Raccoon декларират ZooAnimal като виртуален

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

зец на ZooAnimal.

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

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

от ZooAnimal Panda може да не вика по име ZooAnimal в списъка

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

са едно изключение. Ще обясним защо.

Panda съдържа единствен образец на ZooAnimal, споделян от

Raccoon и Bear. Raccoon и Bear, обаче, едновременно явно инициа-

лизират ZooAnimal. Образец на Panda не може да бъде инициализиран

двукратно.

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

в действителност извличан. Например, не Raccoon или Bear, а Panda

е в действителност извлечен от ZooAnimal. Panda може явно да

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

тор. Ако конструкторът на Panda не инициализира явно ZooAnimal,

извиква се конструкторът по подразбиране на ZooAnimal. Никога

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

Panda чрез Raccoon и Bear.

Конструкторът на Panda може да бъде дефиниран по следния

начин:
Panda::Panda( char *nm )

: ZooAnimal( nm, PANDA ),

Bear( nm ), Raccoon( nm ) { ... }

366


-----------
Panda::Panda( char *nm )

: Bear( nm ), Raccoon( nm ) { ... }

извиква конструктора по подразбиране на ZooAnimal.
Достъп до членовете на виртуален базов клас
Всеки обект от класовете Raccoon и Bear поддържа свое собствено

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

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

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

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

от виртуален и на невиртуален базов клас; единствената разлика е

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

клас.


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

жа долепващи се базова и извлечена част на класа (виж Фигура 7.4 на

страница 321). Обект от класа Bear, например, има базова част ZooAnimal

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

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

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

чения клас. Това е илюстрирано за класовете Bear, Raccoon и Panda

на Фигура 8.3.

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

public и private, както и при невиртуално извличане:

* Онаследените членове при public виртуално извличане запазват

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

* Онаследените членове при private виртуално извличане стават private

членове на извлечения клас.

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

Panda, включва едновременно public и private образци на един виртуален

базов клас? Например,

class ZooAnimal {

public:

void locate();



protected:

short zooArea;

};
class Bear : public virtual ZooAnimal { /* ... */ };

class Raccoon : private virtual ZooAnimal { /* ... */ };


class Panda : public Bear, public Raccoon { /* ... */ };

367


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

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

L Raccoon part L L L virtual L

L L L L ZooAnimal L

єЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇL L L part L

L єЇЇЇЇЇЇЇЇЇ- L L

ЁЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ- ЁЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ-
class Raccoon : public virtual ZooAnimal

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

L Bear part L L L virtual L

L L L L ZooAnimal L

єЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇL L L part L

L єЇЇЇЇЇЇЇЇЇ- L L

ЁЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ- ЁЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ-
class Bear : public virtual ZooAnimal
-ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ¦ -ЇЇЄЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ¦

L Bear part L L L virtual L

L L L L ZooAnimal L

L L L L part L

L L L L L

єЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇL L ЁЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ-

L єЇЇЇЇЇЇЇЇЇL

єЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇL L

L L L

L Raccoon part L L



L L L

L L L


єЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇL L

L єЇЇЇЇЇЇЇЇЇ-

єЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇL

L L


L Panda part L

L L


L L

ЁЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ-


class Panda : public Bear, public Raccoon

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

Panda наследява само едно копие на zooArea и locate(), защото

ZooAnimal е виртуален базов клас. Въпросът е дали на Panda е разрешен

достъпа до тези членове? При public онаследяване Panda има достъп до

тези два члена на ZooAnimal. Обаче, те не са достъпни за Panda при

private онаследяване. Кой вид онаследяване се взема впредвид при

виртуално извличане? Вярно ли е следното обръщение?


Panda p;

p.locate();

Да, обръщението е вярно. Взема се впредвид public онаследяването.

Panda има достъп до zooArea и locate(). Като цяло, независимо от броя

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

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

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

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

Обръщението
Bear b;

b.locate(); // Bear instance


вика образеца на Bear, a не този на ZooAnimal. При невиртуално извличане

обръщението


Panda p;

p.locate(); // which locate?


e двузначно, ако Panda не дефинира свой собствен образец на locate(). Ако

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

се вика образеца locate() на Bear. Двузначността е премахната. При

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

функцията. Затова се взема образеца locate() на Bear, а не този на

ZooAnimal, наследен в Raccoon.


Ред на конструкторите и деструкторите


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

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

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

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

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

class TeddyBear : public Bear,

public virtual ToyAnimal { ... };
TeddyBear pooh;

Извикването на конструкторите за pooh става в следния ред:


ToyAnimal(); // virtual base class

ZooAnimal(); // base class of Bear;

Bear(); // nonvirtual base class

TeddyBear();

Редът на извикване на деструкторите е обратен.

369


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

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

базови класове. Като имиме впредвид това, можем да кажем, че се следва

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

на извлечен клас.

По-сложен е случаят, в който виртуалният базов клас е nested

в йерархията на онаследяването. Например, Panda съдържа споделения

от Bear и Raccoon виртуален базов клас ZooAnimal. Откриването на

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

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

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

клас:


class Panda : public Endangered, public Herbivore,

public Raccoon, public Bear { ... };


Panda yin Yang;
Действителното виртуално извличане от ZooAnimal се появява много

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

Bear. За yinYang редът на обръщенията към конструкторите е следния:
ZooAnimal(); // virtual base class

Herbivore(); // base class declaration order

Endangered();

Raccoon();

Bear();

Panda();


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

претърсва йерархията на онаследяването по реда на декларациите на

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

се претърсва по следния ред:


* Endangered
* Herbivore
* Raccoon
* Bear

Ako Endangered също съдържаше виртуален базов клас, редът за

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

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

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

свързан едновременно с Bear и Raccoon. Редът на извикване на деструкто-

рите е обратен.

Едновременно използване на виртуални и невиртуални образци


Когато едно извличане съдържа едновременно виртуални и невиртуални

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

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

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

невиртуално извлечен от ZooANimal, Panda ще съдържа два образеца

на ZooAnimal, виртуалния образец от Bear и Raccoon и невиртуалния

образец от Endangered.

Отново съществуват два или повече образци на онаследените

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

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


Резюме
По подразбиране механизмът на онаследяване дефинира дърво на

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

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

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

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

в йерархията на извличането, генерира се само един негов образец.

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

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

Bear и Raccoon. Panda, извлечен от Bear и Raccoon, съдържа единстве-



ния споделен образец на ZooAnimal.
Каталог: 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
отнасят до администрацията

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