jcp, PLIK11


Rozdział 11

Dziedziczenie

Klasy bazowe i klasy pochodne .1 Dzcedziczecaie polega na ustanowieniu związku

pomiędzy dwoma klasami, z których jedna jest klasa bazowd, a druga klasa pochodrcc~, czyli klasą dziedzi­czącą. Po zdefiniowaniu takiego związku definicja klasy pochodnej jest rozsze­rzana o wskazane składowe pochodzące z klasy bazowej (zarówno dane składo­we, jak i funkcje składowe). Obiekty klasy pochodnej zawierają więc dane specy­ficzne dla klasy pochodnej (określone w definicji klasy pochodnej) oraz dane odziedziczone z klasy bazowej (określone w definicji klasy bazowej). Podobnie zestaw funkcji składowych, które można aktywizować dla obiektów klasy po­chodnej, składa się z funkcji zdefiniowanych w klasie pochodnej i z funkcji odziedziczonych z klasy bazowej. Dziedziczenie zadaje się, umieszczając w defi­nicji klasy pochodnej po jej identyfikatorze identyfikator klasy bazowej, poprze­dzony słowem kluczowym wskazującym tryb dziedziczenia:

elass id klasy~ochodrtej : tryb d<'.iedz.iczercia id klasy bazowej { cialo klasy~ochodnej }

Tryb dziedziczenia wskazywany jest za pomocą słów kluczowych public, pro­tected lub private. Omówienie trybów dziedziczenia zostanie przedstawione w dalszej części tego rozdziału.

0x01 graphic

204

Rozdziaf Il. Dziedziczenie

class Publikacja // klasa bazowa i

public: char *m szTytuł ; float m flCena ;

void ZmianaCeny ( float flNowaCena )

m flCena = flNowaCena ;

i

void WyświetlTytuł ( )

cout « m szTytuł ;

i i~

class Ksiażka : public Publikacja public:

char *m szAutor ;

void WyświetlAutora ( )

cout « m szAutor ;

i~

class Czasopismo : public Publikacja t

public: char `m szCykl ;

// klasa pochodna

// klasa pochodna

r

Il.l. Klaso bazowe i klasy pochodne

void WyświetlCykl ( )

cout « m szCykl ;

Klasa bazowa Publikacja zawiera podstawowe składowe m szTytuł i m flCena opisujące dowolną publikację oraz funkcje składowe ZmianaCeny i WyświetlTy­tuł. Klasa pochodna Ksiażka wprowadza dodatkową składową określającą autora i funkcję umożliwiającą wyświetlanie na ekranie danych o autorze. Obiekty klasy Książka będą więc zawierały składowe: m szTytuł, m flCena, m szAutor; można dla nich aktywizować funkcje składowe ZmianaCeny, WyświetlTytuł i Wyświetl­Autora. Klasa pochodna Czasopismo wprowadza natomiast składową m szCykl, określającą typ czasopisma (dziennik, tygodnik, ...) i funkcję składową Wyświetl­Cykl. Obiekty tej klasy zawierają składowe m szTytuł, m flCena, m szCykl; można dla nich aktywizować funkcje składowe ZmianaCeny, WyświetlTytuł i WyświetlCykl. Budowę obiektów tych trzech klas przedstawia rysunek 11.1. Klasa bazowa jest - jak widać - generalizacją (uogólnieniem) klas pochodnych, klasy pochodne są specjalizacjami klasy bazowej. W programie najczęściej two­rzy się jedynie obiekty klas pochodnych - klasa bazowa służy jako definicja skła­dowych wspólnych dla wszystkich klas pochodnych.

Publikacja Książka Czasopismo

pszTytuł ysz I'ytul m szTytuł m f7Cena m_flCena m flCena

m szAutor pszCykl

Rys. 11.1. Budowa obiektów klasy bazowej i klas pochodnych

0x01 graphic

206 Rozdział l1. Dziedziczenie

11.2

Hierarchia klas

Dziedziczenie pojedyncze i wielokrotne 1 1 .G. 1 Klasa pochodna może dziedziczyć z więcej niż jednej

klasy bazowej. Związek klasy pochodnej z jedną klasą ba­zową nazywamy dziedziczenie~rz pojedynczym. Gdy natomiast klasa pochodna jest związana z wieloma klasami bazowymi, występuje dziedziczenie Yvielokrottae. Przedstawiona w poprzednim punkcie klasa bazowa Publikacja oraz klasy po­chodne Książka i Czasopismo są przykładem dziedziczenia pojedynczego. Przy dziedziczeniu wielokrotnym w obiekcie klasy pochodnej występują, poza skła­dowymi specyficznymi tej klasy, składowe odziedziczone z wszystkich klas ba­zowych.

class Samolot // klasa bazowa

public: char *m szRodzajNapędu ; int m nZasięg ;

class Transport // klasa bazowa public:

int m nLiczbaPasażerów ; int m nŁadowność ;

class SamolotPasażerski : public Samolot, public Transport

// klasa pochodna

char 'm szLiniaLotnicza ;

J1.2. Hierarchia klas

207

Obiekty klasy SamolotPasażerski będą miały następujące składowe: m szRodzajNapędu - odziedziczone z klasy bazowej Samolot,

m nZasięg - odziedziczone z klasy bazowej Samolot,

m nLiczbaPasażerów -odziedziczone z klasy bazowej Transport, m nŁadowność - odziedziczone z klasy bazowej Transport,

m szLiniaLotnicza - składowa specyficzna klasy SamolotPasażerski.

Dla obiektów tych można aktywizować funkcje składowe odziedziczone z wszystkich klas bazowych i funkcje składowe specyficzne, zdefiniowane w klasie pochodnej.

Drzewa i grafy dziedziczenia 1 1 .2.2 glasa bazowa pewnej klasy pochodnej może być również

klasą pochodną swojej klasy bazowej. Właściwość ta po­zwala na tworzenie hierarchii klas. Jeżeli w hierarchii występuje jedynie dzie­dziczenie pojedyncze, to hierarchię można przedstawić za pomocą drzewa (rys. 11.2.).

Rys. 11.2. Drzewo dziedziczenia

W drzewie dziedziczenia można dla każdej klasy wskazać liniową hierarchię, począwszy od głównej klasy bazowej (rys. 11.3.). Natomiast gdy w hierarchii występuje dziedziczenie wielokrotne, to można ją przedstawić za pomocą grafa (rys. 1 I .4.).

0x01 graphic

0x01 graphic

208

Ro~dziat Il. D~iedzie=enie

Rys. 11.3. Hierarchia klasy Węglowe

Rys. 11.4. Graf dziedziczenia

Powszechne i prywatne 11 . ~ klasy bazowe

Definiując klasę bazową, można wskazać skła­dowe, które będą dziedziczone przez klasy pochodne.

Są to wszystkie składowe występujące w sekcji public i w sekcji protected.

0x01 graphic

0x01 graphic

11.3. Powszechne i pz.lwatne klasy bazowe

Jak to już było powiedziane, sekcje występujące w definicji klasy określają następujące prawa dostępu do składowych:

sekcja public zawiera składowe powszechne, dziedziczone przez klasy pochodne, dostępne w całym programie,

sekcja private zawiera składowe prywatne, nie podlegające dziedziczeniu, dostępne jedynie dla funkcji składowych klasy i dla funkcji zaprzyjaźnionych, sekcja protected zawiera składowe chronione, dziedziczone przez klasy

pochodne, dostępne dla funkcji składowych klasy bazowej i funkcji zaprzyjaźnionych z tą klasą.

class Rama // klasa bazowa

private: int m_nLiczbaSztuk ; protected:

int m nSzerokość ; int m nWysokość ; public:

char *m szDataPobrania ;

i~

class Akwarela : public Rama // klasa pochodna

public: char *m szAutor ;

char *m szTytuł ;

i~

Obiekty klasy bazowej Rama zawierają trzy składowe określające wysokość i szerokość ramy oraz liczbę sztuk ram o danym rozmiarze. Gdy rama zostanie użyta do oprawy obrazu, tworzony jest obiekt klasy Akwarela, który dziedziczy składowe opisujące rozmiar i datę. W obiekcie tej klasy występują następujące składowe: m szAutor, m szTytuł, m_nSzerokość, m nWysokość, m szpata Pobrania. Klasa pochodna dziedziczy składowe sekcji public i sekcji protected klasy bazowej. Składowe te mogą być dołączane do różnych sekcji klasy pochodnej, zależnie od trybu dziedziczenia (wspomnianego już w punkcie 1 l.l)

0x01 graphic

210 RozcL,iał ll. DziecLiczenie

przy dziedziczeniu powszechnym, zadawanym za pomocą słowa kluczowego public

- składowe dziedziczone z sekcji public klasy bazowej są dołączane do sekcji public klasy pochodnej,

- składowe dziedziczone z sekcji protected klasy bazowej są dołączane do i sekcji protected klasy pochodnej,

przy dziedziczeniu chronionym, zadawanym za pomocą słowa kluczowego protected, zarówno składowe dziedziczone z sekcji public jak i z sekcji pro­tected są dołączane do sekcji protected klasy pochodnej,

przy dziedziczeniu prywatnym, zadawanym za pomocą słowa kluczowego private, zarówno składowe dziedziczone z sekcji public jak i z sekcji protec­ted klasy bazowej sądołączane do sekcji private klasy pochodnej.

Przy dziedziczeniu powszechnym składowe odziedziczone z klasy bazowej nie zmieniają więc swego charakteru, przy dziedziczeniu chronionym wszystkie .r;,

odziedziczone składowe stają się składowymi chronionymi, a przy dziedziczeniu prywatnym składowe odziedziczone stają się składowymi prywatnymi klasy pochodnej.

`~~~~' class KIasaPierwsza

private: int m_nAtrybutPierwszy ; protected:

int m nAtrybutDrugi ; public:

int m_nAtrybutTrzeci ; ?;

class KIasaDruga : public KIasaPierwsza

/* składowa m_nAtrybutDrugi zostanie dołączona do sekcji protected: składo­wa m nAtrybutTrzeci zostanie dołączona do sekcji public: (sekcje public: i protected: mogąca klasie KIasaDruga bezpośrednio nie występować) */

11.3. Powszechne i pywahte klasy bazowe 211

class KIasaTrzecia : private KIasaPierwsza i

/* składowe m nAtrybutDrugi i m nAtrybutTrzeci zostaną dołączone do sekcji private: (sekcja private: może w klasie KIasaTrzecia bezpośrednio nie wy­stępować) */

i;

class KIasaCzwarta : public KIasaDruga i

/* dziedziczone są składowe m_nAtrybutDrugi i m nAtrybutTrzeci */ };

class KIasaPiąta : public KIasaTrzecia (

/* z klasy bazowej nie są dziedziczone żadne składowe */

i;

Tak więc z poziomu programu do składowej m_nAtrybutDrugi można uzyskać dostęp poprzez obiekty klas KIasaPierwsza, KIasaDruga i KIasaCzwarta, a nie można poprzez obiekty klas KIasaTrzecia i KIasaPiąta. W klasie pochodnej można zmieniać charakter dziedziczonych składowych, wskazując bezpośrednio za pomocą operatora zakresu ( :: ), do której sekcj i składowa ma zostać dołączona.

class KIasaSzósta i

public: int m nAtrybutCzwarty ; int m nAtrybutPiąty ;

i;

class KIasaSiódma : protected KIasaSzósta public:

KIasaSzósta : : m_nAtrybutCzwarty ;

0x01 graphic

212 Rozd~iai ll. Dzied=żczenie i

1 /" składowa m_nAtrybutCzwarty odziedziczona z klasy bazowej KIasaSzósta ' zostanie dołączona do sekcji public zamiast do sekcji protected */

/* składowa m nAtrybutPiąty zostanie dołączona do sekcji protected '/ };

Wirtualne klasy bazowe 11 ~ -T W grafie dziedziczenia może wystąpić sytuacja,

w której klasa pochodna dziedziczyłaby wielokrotnie z tej samej klasy bazowej. Aby zapobiec wielo­krotnemu powielaniu tych samych składowych, dziedziczonych z klas bazowych, należy w klasie pochodnej zadeklarować wirtualną klasę bazową. Dokonuje się tego przez umieszczenie słowa kluczowego virtual przed określeniem trybu ;!'I dziedziczenia klasy bazowej.

class Zwierzęta u~'' {

public: char "m szNazwaGatunkowa ; };

class Ssaki : virtual public Zwierzęta class Morskie : virtual public Zwierzęta

class Orka : public Ssaki, public Morskie

Dzięki wprowadzeniu wirtualnej klasy bazowej Zwierzęta, obiekty klasy Orka będą miały tylko jedną składową o identyfikatorze m szNazwaGatunkowa.

11.5. Obiekt~~ klas pochodyych 213

11.5

Obiekty klas pochodnych

Tworzenie

Podczas tworzenia obiektu klasy pochodnej wywoływane są konstruktory klas bazowych, począwszy od klasy naj­starszej w hierarchii.

class Statki i

protected: float m flTonaż ; Statki ( )

i

m flTonaż = 1000 ; i

i.

class Handlowe : public Statki i

protected: float m flŁadowność ; Handlowe ( )

f m flŁadowność = 700 ; ?;

//

0x01 graphic

214 Rozdział Il. Driedziczenie

class Kontenerowce : public Handlowe i

public: int m_nLiczbaKontenerów ;

Kontenerowce ( ) {

m_nLiczbaKontenerów = 70 ;

} i

};

;j~ Kontenerowce KontenerowiecStandardowy ; ,~i

W przykładzie tym jest tworzony obiekt KontenerowiecStandardowy klasy Kon­tenerowce. Konstruktory inicjujące ten obiekt zostaną wywołane w kolejności: Statki, Handlowe, Kontenerowce i nadadzą składowym standardowe wartości:

m flTonaż == 1000 m flŁadowność == 700

m_nLiczbaKontenerów == 70

Jeżeli konstruktory klas bazowych wymagają argumentów, to należy wywołania tych konstruktorów umieścić na liście powiązań składowych i argumentów konstruktora klasy pochodnej (lista ta była już omawiana w rozdziale 9). Składowe, których wartości początkowych nie ustalają konstruktory klas bazowych, mogą być inicjowane przez konstruktor klasy pochodnej. W konstruktorze tym można również dokonać zmiany wartości składowych, które zostały uprzednio zainicjowane przez konstruktory klas bazowych (konstruktor klasy pochodnej ma więc ostatnie słowo w procesie inicjowania obiektu klasy pochodnej).

class Statki {

protected: float m flTonaż ; char *m szArmator ;

Statki ( float flTonaż ) : m flTonaż ( flTonaż ) { } } ;

11.5. Obiekt~~ klas poc/zodnych

class Handlowe : public Statki {

protected: float m flŁadowność ;

Handlowe ( float flTonaż, float flŁadowność)

Statki ( flTonaż ), m flŁadowność ( flŁadowność ) { } i;

class Kontenerowce : public Handlowe {

public; int m nLiczbaKontenerów ;

Kontenerowce ( float flTonaż, float flŁadowność,

int nLiczbaKontenerów, char* szArmator) :

Handlowe ( flTonaż, flŁadowność ), m_nLiczbaKontenerów ( flLiczbaKontenerów )

armator = new char [ strlen ( szArmator ) + 1 ] ;

strcpy ( m szArmator, szArmator ) ;

i }>

Kontenerowce KontenerowiecPopularny ( 2500, 1750, 100, "Żegluga Bałtycka" ) ;

W tym przypadku podczas deklarowania obiektu klasy Kontenerowce należy podać argumenty, które będą wykorzystane przy wywołaniach kolejnych kon­struktorów.

Przy usuwaniu obiektu klasy pochodnej destruktory są wywoływane w kolej­ności odwrotnej do kolejności wywołania konstruktorów.

0x01 graphic

2,16 Ro=dniał Il. Daiedaiczenłe

Dostęp do składowych 11 5 2

Ogólne zasady dostępu do składowych obiektu klasy pochodnej są takie same jak dla obiektów klas nie

dziedziczących składowych. Wyjaśnienia wymaga jednak kilka zagadnień szczegółowych.

Dziedziczenie składowych o takich samych identyfikatorach

Gdy w kilku klasach bazowych występują składowe podlegające dziedziczeniu o takich samych identyfikatorach, to dziedzicząca wielokrotnie klasa pochodna będzie zawierała kilka składowych o takim samym identyfikatorze. W odwołaniach do tych składowych należy identyfikator składowej poprzedzać identyfikatorem klasy bazowej z operatorem zakresu ( :: ). Ta wkaściwość dziedziczenia uniemożliwia wprowadzanie dziedziczonych funkcji składowych o przeciążonym identyfikatorze (przeciążanie identyfikatora jest możliwe jedynie dla funkcji składowych tej samej klasy).

c~ass Ptaki

public: char ~m szNnazwa ;

char m cNielotne ;

void Drukuj ( chat " szTekst = " " )

cout « szTekst « m szNazwa ;

} i~

class ZwierzętaDomowe {

public: chat m szNazwa ;

~i

void Drukuj ( )

{ cout « "Rodzaj zwierząt domowych : " « m szNazwa ; } } ;

l L5. Ohiekh~ klas pocho~lm~c/z

class Indyk : public Ptaki, public ZwierzętaDomowe public:

int m_nNumer ; float m flWaga ; );

Indyk indyk ;

indyk . m szNazwa = "xxx " ; // błąd, niejednoznaczność indyk . Ptaki::m szNazwa = "Meleagris gallopavo" ;

indyk . ZwierzętaDomowe::m szNazwa = "Drób" ; indyk . m cNielotne = 1 ;

indyk . m nNumer = 157 ; indyk . m flWaga = 12.4 ;

indyk . Drukuj ( ) ; // błąd, niejednoznaczność indyk . Ptaki::Drukuj ( "Gatunek ptaków : " ) ;

indyk . ZwierzętaDomowe::Drukuj ( ) ;

Wskazywania klasy bazowej, z której odziedziczona została składowa, można oczywiście uniknąć, nadając wszystkim składowym hierarchii klas unikatowe identyfikatory.

Dziedziczenie a funkcje zaprzyjaźnione

W definicji funkcji zaprzyjaźnionej klasy można odwoływać się do wszystkich składowych tej klasy (powszechnych, chronionych i prywatnych). Związek zaprzyjaźnienia nie podlega jednak dziedziczeniu. Jeżeli klasa posiadająca funkcję zaprzyjaźnioną jest klasą bazową pewnej klasy pochodnej, to w funkcji zaprzyjaźnionej nie można odwoływać się do składowych prywatnych i chronionych tej klasy pochodnej. Wyjątkiem są składowe chronione klasy pochodnej, które zostały odziedziczone. z klasy bazowej - do takich składowych ma dostęp funkcja zaprzyjaźniona klasy bazowej. Natomiast funkcja zaprzyjaźniona klasy pochodnej nie może odwoływać się do składowych prywatnych i chronionych klasy bazowej.

0x01 graphic

218

Ro=dual lI. D=iecLiezenie

class Deser f

friend void Rozliczenie ( ) ;

private:

char *m szMarża ;

protected:

char *m szPrzepis ;

public:

char *m szNazwa ;

float m flCena ;

...

i;

class Lody : public Deser f

friend void Przygotowanie ( ) ;

private:

char m cBakalie ;

protected:

char "m szKalkulacja ;

public:

char *m szSmak ;

}; void Rozliczenie ( ) { Deser deser;

Lody lody ;

deser . m szMarża ; deser . m szPrzepis ; deser . m szNazwa ;

// funkcja zaprzyjaźniona klasy Deser

// poprawnie // poprawnie // poprawnie

I L5. Obiekty klas pochodnych 219

deser , m flCena ;

lody . m cBakalie ;

1 _

j lody . m szKalkulacja ;

i

lody . m szPrzepis ;

lody . m szSmak ;

lody . m szNazwa ;

lody . m flCena ;

void Przygotowanie ( ) { Deser deser ;

Lody lody ;

i deser . m szMarża ; deser . m szPrzepis ; i

deser , m szNazwa ; deser . m flCena ; lody . m cBakalie ;

r lody . m szKalkulacja ; lody . m szPrzepis ;

i lody . m szSmak ; j _

lody . m szNazwa ; lody . m flCena ;

// poprawnie // błąd

l/ błąd

// poprawnie ( ! ) // poprawnie

// poprawnie // poprawnie

// funkcja zaprzyjaźniona klasy Lody

/l błąd // błąd

// poprawnie // poprawnie // poprawnie // poprawnie // poprawnie // poprawnie // poprawnie // poprawnie

i

W funkcji Rozliczenie, będącej funkcją zaprzyjaźnioną klasy Deser, można odwołać się do składowej chronionej m szPrzepis obiektu klasy pochodnej Lody. Składowa ta została bowiem odziedziczona z klasy bazowej, z którą zaprzyjaźniona jest funkcja Rozliczenie. Podobne odwołanie do składowej chronionej m szKalkulacja obiektu klasy pochodnej jest niepoprawne (składowa ta nie została odziedziczona z klasy bazowej).

0x01 graphic

' 220 Ro~cG:ic~t II. D~ied~iczenie I',

Zakres identyfikatorów Po napotkaniu w klasie pochodnej identyfikatora zmiennej lub funkcji kompilator poszukuje definicji tej zmiennej czy funkcji, sprawdzając kolejno:

czy jest to identyfikator składowej klasy pochodnej,

czy jest to identyfikator składowej klasy bazowej występującej w hierarchii dziedziczenia klasy pochodnej,

czy jest to identyfikator zadeklarowany na poziomie pliku.

Konwersja 11.5.3

,j', Konwersja obiektów klas bazowych i pochodnych

Przy założeniu:

'j dziedziczenia powszechnego ( public ),

• klas bazowych bez składowych prywatnych ( private ),

pomiędzy obiektami klas bazowych i ich klas pochodnych zachodzą następujące konwersje standardowe:

i,; obiekt klasy bazowej można zastąpić obiektem klasy pochodnej,

referencję obiektu klasy bazowej można zastąpić referencją obiektu klasy pochodnej,

wskaźnik obiektu klasy bazowej można zastąpić wskaźnikiem obiektu klasy pochodnej.

Konwersje te są zawsze bezpieczne, ponieważ przy wymienionych założeniach obiekt klasy pochodnej zawiera wszystkie składowe, które występują w obiekcie klasy bazowej. Konwersje odwrotne, wymagające zastąpienia obiektu, referencji czy wskaźnika obiektu klasy pochodnej odpowiednio: obiektem, referencją czy

j wskaźnikiem obiektu klasy bazowej, wymagają zdefiniowania operatora ''I konwersji (punkt 9.8). Obiekt klasy bazowej może bowiem nie zawierać tych wszystkich składowych, które występują w obiekcie klasy pochodnej. Zasady I':

konwersji obiektów klas bazowych i klas pochodnych ilustruje rysunek 1 1.5.

konwersja standardowa

OBIEKT OBIEKT KLASY KLASY POCHODNEJ ~ BAZOWEJ

operator konwersji

Rys. 11.5. Konwersja obiektów

I1.5. Obiekh~ klas pochodnych 221

class Biurko ; //

class Stół i

public: int m nSzerokość ; int m nDługość ; Stół ( int, int ) ;

Stół ( )

operator Biurko ( ) ;

// konstruktor // konstruktor

// operator konwersji

i;

Stół :: Stół ( int nSzerokość, int nDługość ) i

m nSzerokość = nSzerokość ; m nDługość = nDługość ;

i

class Biurko : public Stół (

public: int m_nSzuflady ;

Biurko ( int, int, int ) ; /1 konstruktor

i.

Biurko :: Biurko ( int nSzerokość, int nDługość, int nSzuflady)

Stół ( nSzerokość, nDługość )

m nSzuflady = nSzuflady ;

0x01 graphic

222 Rozdział ll. Dziedziczenie

Stół :: operator Biurko ( ) (

j return Biurko ( m nSzerokość, m nDługość, 3 ) ;

i

Biurko NoweBiurko ( 40, 60, 5 ) ;

Biurko StareBiurko = NoweBiurko ; // zgodność

Biurko &rBiurko = NoweBiurko ; // zgodność

Biurko 'pBiurko = &NoweBiurko ; // zgodność

Stół NowyStół ( 50, 80 ) ;

Stół StaryStół = NowyStół ; // zgodność

Stół &rStół = NowyStół ; // zgodność

Stół *pStół = &NowyStół ; /1 zgodność

StaryStół = NoweBiurko ; // konwersja standardowa

rStół = NoweBiurko ; // konwersja standardowa

pStół = &NoweBiurko ; // konwersja standardowa

StareBiurko = NowyStół ; // operator konwersji

StareBiurko = Biurko ( NowyStół ) ; // operator konwersji

rBiurko = NowyStół ; // operator konwersji

rBiurko = Biurko ( NowyStół ) ; // operator konwersji

pBiurko = &NowyStół ; // bład

pBiurko = &Biurko ( NowyStół ) ; // operator konwersji

Automatyczna konwersja obiektu (referencji, wskaźnika) klasy pochodnej na

obiekt (referencję, wskaźnik) klasy bazow ej wprowadza jednak pewne ogranicze-

nia. Nie można bowiem po jej dokonaniu odwoływać się do składowych klasy

pochodnej, które nie zostały odziedziczone z klasy bazowej.

Stół *pStolik ;

Biurko Mebel ( 45, 75, 3 ) ;

pStolik = &Mebel ; // konwersja standardowa

I LS. Obiekh~ klas pochodnych 223

int nSzerokość = pStolik -> m_nSzerokość ; // poprawnie int nSzuflady = pStolik -> m nSzuflady ; // błąd

Rozwiązanie tego problemu jest możliwe za pomocą funkcji wirtualnych przed­stawionych w następnym rozdziale.

Gdy obiekty, wskaźniki lub referencje klas bazowych i pochodnych są przekazywane jako argumenty funkcji, przy uzgadnianiu typów (klas) argumentów formalnych i aktualnych mogą wystąpić następujące przypadki:

bezpośrednia zgodność typów (ta sama klasa),

zgodność uzyskana po konwersji standardowej (klasa pochodna zastępuje klasę bazową),

zgodność uzyskana po konwersji zadanej w programie za pomocą operatora konwersji lub za pomocą konstruktora,

niezgodność.

class Drukarka ; class Kserograf {

protected: char *m szFormat ; char *m szToner ; public:

operator Drukarka ( ) ; // operator konwersji class Laser

{ protected:

float m flDługośćFali, m flMoc ; i~

class Drukarka : public Kserograf, public Laser protected:

char *m szSprzęg ;

0x01 graphic

224 Rozdziaf Il. Dziedziczenie

!: char *m szJęzykSterowania ; public:

Drukarka ( Laser ) ; // konstruktor

i;

Kserograf ksero ; Laser laser ; Drukarka drukarka ; //

void FunkcjaPierwsza ( Kserograf ) ;

E FunkcjaPierwsza ( ksero ) ; // zgodność bezpośrednia ;I f

I FunkcjaPierwsza ( laser ) ; // błąd

FunkcjaPierwsza ( drukarka ) ; // konwersja standardowa ! //

void FunkcjaDruga ( Laser ) ;

FunkcjaDruga ( laser ) ; // zgodność bezpośrednia FunkcjaDruga ( ksero ) ; // błąd

FunkcjaDruga ( druk ) ; // konwersja standardowa i

i //

void FunkcjaTrzecia ( Drukarka ) ; i

FunkcjaTrzecia ( drukarka ) ; // zgodność bezpośrednia FunkcjaTrzecia ( ksero ) ; // operator konwersji

I! FunkcjaTrzecia ( laser ) ; // konwersja za pomocą konstruktora //

Podany sposób uzgadniania typów jest również stosowany przy wyborze wersji funkcji o przeciążonym identyfikatorze, której argumentami są obiekty klas. Gdy

dla danego sposobu uzgadniania możliwy jest wybór więcej niż jednej funkcji, . kompilator sygnalizuje błąd niejednoznaczności.

11.5. Obiekty klas hochodn~~ch 225

Konwersja wskaźników składowych klas bazowych i klas pochodnych

W przypadku wskaźników składowych obiektów (punkt 9.4.4) klas bazowych i klas pochodnych obowiązująnastępujące reguły konwersji:

wskaźnik składowej obiektu klasy pochodnej można zastąpić wskaźnikiem składowej obiektu klasy bazowej (konwersja standardowa),

aby zastąpić wskaźnik składowej obiektu klasy bazowej wskaźnikiem składowej obiektu klasy pochodnej, należy zdefiniować operator konwersji. Regułę tę, która jest jakby odwrotnością reguł konwersji obiektów klas bazowych i klas pochodnych, ilustruje rysunek 11.6.

WSKAŻNIK operator konwersji WSKAŻNIK SKŁADOWEJ SKŁADOWEJ KLASY KLASY POCHODNEJ BAZOWEJ

Konwersja standardowa

Rys. 11.6. Konwersja składowych obiektów

class Strunowe

public: int m_nLiczbaStrun ; }~

class Gitara : public Strunowe protected:

char m cAkustyczna ; } ;

int Strunowe :: *pStruny ;

int Gitara :: *pTony ;

pStruny = &Strunowe :: m nLiczbaStrun ;

plony = &Gitara :: m nLiczbaStrun ;

0x01 graphic

226 Rozc~iał ll. Dziedziczenie

pStruny = plony ; // błąd

plony = pStruny ; // konwersja standardowa

Identyfikacja obiektów 11 ~ ~ Po zdefiniowaniu hierarchii klas pochodnych pew­

nej klasy bazowej można, dzięki konwersji standar­dowej, zastępować obiekty, referencje lub wskaźniki klasy bazowej obiektami, referencjami lub wskaźnikami klas pochodnych. Na przykład, wartością zmiennej zadeklarowanej jako wskaźnik obiektu klasy bazo­wej może być wskaźnik obiektu dowolnej klasy pochodnej, dziedziczącej z tej klasy bazowej. Powstaje w związku z tym problem, w jaki sposób stwierdzić, do której z klas pochodnych należy obiekt, wskazywany przez wartość zmiennej wskaźnikowej. Problem ten można rozwiązać na dwa sposoby. Po pierwsze do klas pochodnych można wprowadzić skladowe typu i bezpośrednio w programie analizować ich wartości. Druga metoda, polegająca na definiowaniu funkcji wir­tualnych przenosi ciężar rozpoznawania klasy pochodnej na kompilator i system wykonawczy. Fccnkcja wirtualna, zdefiniowana w klasie bazowej, jest następnie redefiniowana (przeciążana) w klasach pochodnych. Podczas wykonywania pro­gramu wywołanie funkcji wirtualnej może nastąpić za pośrednictwem zmiennej wskaźnikowej, która wskazuje na obiekt jednej z klas pochodnych. System wy­konawczy wywołuje wówczas wersję funkcj i wirtualnej, którą zdefiniowano w tej właśnie klasie pochodnej.

11.6.1

Składowe typu

Wprowadzanie składowych typu do definicji klas po­

chodnych pozwala na bezpośrednie stwierdzenie, do której klasy pochodnej należy obiekt, wskazywany przez pewną zmienną wskaźnikową lub referencyjną. W celu ustalenia klasy pochodnej należy zastosować ciąg in­

77.6. Iderahfikacja obiektów 227

strukcji warunkowych lub instrukcję wyboru. Powoduje to niekiedy znaczne zwiększenie wielkości programu, a ponadto wymaga modyfikowania instrukcji testujących każdorazowo po dodaniu lub usunięciu klasy pochodnej.

class Obuwie // klasa bazowa f

public:

char m cTyp ;

Obuwie ( ) // konstruktor

f

m cTyp = 1 ;

};

class Letnie : public Obuwie // klasa pochodna

f

public:

Letnie ( ) // konstruktor

f

m cTyp = 2 ;

i;

class Zimowe : public Obuwie // klasa pochodna

f

public:

Zimowe( ) // konstruktor

f

m cTyp = 3 ;

i;

0x01 graphic

228

Rozd=iał Il. Dziedziczenie

class Sandały : public Letnie f

public: Sandały ( ) f

m cTyp = 4 ;

// klasa pochodna

// konstruktor

) );

class Półbuty : public letnie // klasa pochodna f

public: Półbuty ( ) f

// konstruktor

m cTyp = 5 ;

)

}; obuwie ;

Obuwie

Letnie l etnie ;

Zimowe zimowe ;

Sandały sandały ;

Półbuty półbuty ;

Obuwie *pBut ;

Obuwie &rBut = obuwie ;

pBut = &sandały ;

raut = zimowe ;

// obiekty klas

// zmienna wskaźnikowa // zmienna referencyjna

// przykładowe przypisania

l 1.6. Identyfikacja obiektów 229

switch ( pBut -> m cTyp ) i

case 1 : .............. case 2 : .............. case 5 : .............. default : ..............

// instrukcja testująca

// obiekt klasy Obuwie

// obiekt klasy Letnie

// obiekt klasy Półbuty

// obiekt nieznany

i

if ( raut . m cTyp == 3 ) // instrukcja testująca ........... // obiekt klasy Zimowe

Funkcje wirtualne 1 1 .6.2 Fcrnkcje wirtualne definiowane są dla pewnej hierarchii

klas. Jest to rodzina funkcji o takim samym typie wyniku, takim samym identyfikatorze i tej samej sygnaturze (czyli liczbie i typach argumentów) definiowanych w klasie bazowej i w klasach pochodnych. Funkcję wirtualną wyróżnia się, poprzedzając w ciele klasy jej deklarację lub definicję słowem kluczowym virtual. Słowo to musi być użyte w klasie bazowej, będącej korzeniem hierarchii klas, natomiast w klasach pochodnych nie jest konieczne jego powtarzanie.

class DrzewaKrzewy // klasa bazowa

{ protected:

char 'm szNazwaParku, 'm szNazwaGatunkowa ; float m flWysokość ;

public:

virtual void Wyświetl ( ) i

cout « endl « "Drzewa i krzewy rosnące w " « m szNazwaParku « endl ;

0x01 graphic

230

Rozd~iat ll. Dziedziczenie

void Wspólne ( ) i

cout « endl « m szNazwaGatunkowa « endl « m flWysokość « endl ;

i }>

class Drzewa : public DrzewaKrzewy // klasa pochodna i

char *m szPień ; public:

void Wyświetl ( ) i

Wspólne ( ) ;

cout « m szPień « endl ;

i i>

class Krzewy : public DrzewaKrzewy i

private: chat `m szPokrój ; public:

void Wyświetl ( ) i

Il klasa pochodna

Wspólne ( ) ;

cout « m szPakrćj « end! ;

17.6. Idenhfkacja obiektów 231

W przykładzie tym, w definicji każdej z klas zawarta jest definicja funkcji Wy­świetl. W klasie bazowej DrzewaKrzewy funkcja ta została zadeklarowana jako funkcja wirtualna - definicje znajdujące się w klasach pochodnych są więc jej przeciążeniami. Jak widać, wersja funkcji Wyświetl związana z daną klasą powoduje wyprowadzenie na ekran monitora wartości odpowiednich składowych. Dla klasy bazowej jest to m szNazwaParku a dla klas pochodnych są to składowe wspólne m szNazwaGatunkowa i m flWysokość oraz składowe specyficzne m szPień i m szPokrój. Po zadeklarowaniu zmiennej wskaźnikowej:

DrzewaKrzewy *pDrzewoKrzew ;

można nadawać jej wartości będące wskaźnikami obiektów należących do klas DrzewaKrzewy, Drzewa i Krzewy (konwersja standardowa). Gdy po dokonaniu przypisania:

pDrzewoKrzew = new Drzewo ;

za pośrednictwem zmiennej pDrzewoKrzew zostanie zaktywizowana funkcja wirtualna

pDrzewoKrzew -> Wyświetl ( ) ;

to system wykonawczy wywoła wersję tej funkcji zdefiniowaną w klasie Drzewa. Wersja wywoływanej funkcji wirtualnej zależy więc od klasy, do której należy obiekt wskazywany przez zmienną.

pDrzewoKrzew = new DrzewaKrzewy ;

pDrzewoKrzew -> Wyświetl ( ) ; // wersja klasy DrzewaKrzewy pDrzewoKrzew = new Krzewy ( ) ;

pDrzewoKrzew -> Wyświetl ( ) ; // wersja klasy Krzewy

W celu zapamiętania danych o drzewach i krzewach znajdujących się w parku można również utworzyć tablicę, zadeklarowaną jako tablicę wskaźników obiektów klasy DrzewaKrzewy. Wywołanie funkcji wirtualnej za pośrednictwem elementu takiej tablicy powoduje wywołanie wersji funkcji odpowiedniej do klasy obiektu wskazywanego przez ten element.

DrzewaKrzewy' apTabIicaDrzewKrzewów [ 32 ) ; apTabIicaDrzewKrzewów [ 0 ] = new DrzewaKrzewy ; apTabIicaDrzewKrzewów [ 1 ] = new Krzewy ; apTabIicaDrzewKrzewów [ 2 ] = new Drzewa ;

0x01 graphic

232

Rozdziat ll. D~ied~ic-enie

apTabIicaDrzewKrzewów [ 0 ] -> Wyświetl ( ) ; // wersja klasy DrzewaKrzewy apTabIicaDrzewKrzewów [ 1 ] -> Wyświetl ( ) ; // wersja klasy Krzewy apTabIicaDrzewKrzewów [ 2 ] -> Wyświetl ( ) ; // wersja klasy Drzewa

Funkcje wirtualne umożliwiają rozwiązanie przedstawionego w poprzednim roz­dziale problemu dostępu do składowych specyficznych klasy pochodnej za po­średnictwem wskaźnika zadeklarowanego pierwotnie jako wskaźnik obiektów klasy bazowej. W deklaracji funkcji wirtualnej Wyświetl znajdującej się w klasie pochodnej Drzewa wystąpiło odwołanie do składowej specyficznej tej klasy

', m szPień (składowa ta nie jest dziedziczona z klasy bazowej). Podobnie w wersji funkcji wirtualnej Wyświetl zdefiniowanej w klasie Krzewy występuje odwołanie lill

do składowej specyficznej m szPokrój. Wywołania tej funkcji wirtualnej są na­tomiast realizowane za pośrednictwem zmiennej zadeklarowanej jako wskaźnik obiektów klasy bazowej DrzewaKrzewy.

Funkcje wirtualne mogą być również aktywizowane statycznie za po­średnictwem obiektów lub ich referencji czy wskaźników.

Drzewa brzoza ;

Drzewa &rDrzewo = brzoza ;

Drzewa *pDrzewo = &brzoza ; brzoza . Wyświetl ( ) ;

rDrzewo . Wyświetl ( ) ; pDrzewo -> Wyświetl ( ) ;

brzoza . DrzewaKrzewy :: Wyświetl ( ) ; rDrzewo . DrzewaKrzewy :: Wyświetl ( ) ; pDrzewo -> DrzewaKrzewy :: Wyświetl ( ) ;

Ostatnie trzy instrukcje powodują wykonanie tej wersji funkcji wirtualnej Wy­świetl, którą zdefiniowano w klasie bazowej DrzewaKrzewy.

Częstą praktyką jest wywoływanie w definicji funkcji wirtualnej klasy po­chodnej wersji tej samej funkcji z jej bezpośredniej klasy bazowej. Dzięki temu na każdym poziomie hierarchii klas (i związanej z nią hierarchii funkcji wirtual­nych) realizowane sąoperacje specyficzne dla tego poziomu. Łączna operacja jest ;,

I1.6. Idetyfikacja obiektów 233 więc realizowana jako złożenie operacji wykonywanych na poszczególnych po­ziomach hierarchii klas.

class MateriałyBudowlane f

public: virtual void Opis ( ) f

cout « " materiał budowlany." « endl ; i

)~ class MateriałyCeramiczne : public MateriałyBudowlane f

public: virtual void Opis ( ) f

cout « " ceramiczny" ; MateriałyBudowlane :: Opis ( ) ; i

).

class Cegła : public MateriałyCeramiczne f

public: virtual void Opis ( ) f

cout « endl « "Cegła to" ; MateriałyCeramiczne :: Opis ( ) ; )

i.

0x01 graphic

234 Rozd~ial ll. D~iec~iczenie

Cegła cegła ;

Zastosowanie funkcji Opis do obiektu cegła spowoduje utworzenie następującego zdania:

Cegła to ceramiczny materiał budowlany.

Kolejne elementy tego zdania zostały wygenerowane na kolejnych poziomach hierarchii klas.

Funkcje wirtualne nie muszą być przeciążane we wszystkich klasach pochodnych hierarchii dziedziczenia. Dla niektórych klas pewne funkcje nie mają bowiem zastosowania i definiowanie ich byłoby bezcelowe. Zdefiniowanie w klasie pochodnej funkcji o identyfikatorze takim samym jak identyfikator funkcji wirtualnej, występującej w klasie bazowej, powoduje albo przesłonięcie funkcji wirtualnej, albo błąd kompilacji. I tak, gdy funkcja zadeklarowana w klasie pochodnej ma, w porównaniu z funkcją wirtualną taki sam typ wyniku, lecz inną sygnaturę, to następuje przesłonięcie funkcji wirtualnej. Jeżeli natomiast funkcja wirtualna i funkcja zdefiniowana w klasie pochodnej różnią się tylko typem wyniku, to sygnalizowany jest błąd kompilacji.

class Nuta // klasa bazowa

protected:

int m nWysokość ; float m_nCzas ;

public: virtual int Wysoka ( ) // funkcja wirtualna i

return m nWysokość ;

); class Ósemka : public Nuta // klasa pochodna

public: a

11.6. lclenty akacja obiektów 235

int Wysoka ( ) // poprawnie, przeciążenie

return m nWysokość + 1 ;

int Wysoka ( int nWysokość ) // poprawnie, przesłanianie i

int nPośrednik = m nWysokość ;

m nWysokość = nWysokość ;

return nPośrednik ;

i

void Wysoka ( ) // błąd

m nWysokość = 5 ;

Klasy abstrakcyjne 1 1 .6.a7 w hierarchii dziedziczenia klasa bazowa znajdująca się

na najwyższym poziomie często służy jedynie jako generalizacja klas pochodnych. Zawiera ona wtedy wyłącznie wspólne składowe, występujące we wszystkich klasach pochodnych, nie występują w niej natomiast żadne składowe specyficzne. W programie często nie tworzy się obiektów klasy bazowej, są tworzone jedynie obiekty klas pochodnych. Powstaje w związku z tym pytanie, czy w takiej uogólniającej klasie bazowej trzeba zamieszczać pełne definicje funkcji wirtualnych, skoro i tak dła obiektu klasy bazowej funkcje te nie będą nigdy aktywizowane. W języku C++ można uniknąć podawania pełnej definicji funkcji wirtualnej w klasie bazowej, deklarując czystcł funkcję n~irtualncl. Jej definicja zawiera po liście argumentów formalnych operator przypisania i wartość 0. Klasa bazowa, w której zdefiniowano chociaż jedną czystą funkcję wirtualną, zwana jest klasc/ abstrakcyjna. W programie nie wolno tworzyć obiektów klasy abstrakcyjnej, jest ona jedynie generalizacją klas pochodnych.

0x01 graphic

2,~() Rozdzial ll. Dziedziczenie I~,

Można natomiast deklarować zmienne, których wartościami będą wskaźniki lub referencje klas abstrakcyjnych.

,. ,:j class TransmisjaSzeregowa Il klasa abstrakcyjna protected:

int m nPrędkość, m nLiczbaBitówZnaku ; public:

'virtual char Nadawanie ( char* ) = 0 ; ll czysta funkcja wirtualna )

i class Asynchroniczna : public TransmisjaSzeregowa I

int m_nLiczbaBitówStopu ; char m cParzystość ; public:

char Nadawanie ( char *szTekst ) ;

?, class Synchroniczna : public TransmisjaSzeregowa

char m cSynchronizacja ; char SumaKontrolna ( char* ) ; public:

char Nadawanie ( char "szTekst ) t char cBłąd = 0 ;

char cSuma = SumaKontrolna ( szTekst ) ;

NadajKomunikat ( BudujKomunikat ( szTekst, cSuma ), &cBłąd ) ; return cBłąd ;

) n ).

i

11.6. Identyfikacja obiektów 23%

char Asynchroniczna :: Nadawanie ( char "szTekst ) f

char cBłąd = 0 ; while ( 'szTekst )

NadajZnak ( BudujZnakAsy (*szTekst++, m_nLiczbaBitówStopu, m cParzystość , &cBłąd ) ;

return cBłąd ; i

I }

TransmisjaSzeregowa "pSzeregowa ; pSzeregowa = new Asynchroniczna ;

pSzeregowa -> Nadawanie ( "Halo, to ja" ) ; // wersja klasy Asynchroniczna pSzeregowa = new Synchroniczna ;

s pSzeregowa -> Nadawanie ( "Co tam słychać po drugiej stronie?" ) ;

', // wersja klasy Synchroniczna

Wirtualne destruktory 11.6.4 Jeżeli destruktor zdefiniowany w klasie bazowej jest

poprzedzony słowem kluczowym virtual, to wszystkie de­struktory zdefiniowane w klasach pochodnych są traktowane jak przeciążenia tego wirtualnego destruktora. Reguła ta obowiązuje, pomimo tego iż ze względu na zasady nazywania, destruktory zdefiniowane w klasach pochodnych mają różne identyfikatory. Właściwość ta pozwala na aktywizowanie destruktorów klas pochodnych za pośrednictwem zmiennych wskaźnikowych, zadeklarowanych dla klasy bazowej.

class Pojedynek

{ public:

virtualPojedynek ( ) // wirtualny destruktor cout « "Pojedynek zakończony." « endl ; }

i;

0x01 graphic

238

Rozd<-iał ll. Dziedziczenie

class Zapalczywy : public Pojedynek i

public: char *m szOstatnieSłowa ; -Zapalczywy ( )

cout « m szOstatnieSłowa « endl ;

i )~

class Mściwy : public Pojedynek

public: int m nLiczbaTrafień ;

Mściwy()

cout « "Trafiłem go " « m nLiczbaTrafień « "razy!" « endl ;

}; Zapalczywy *raptus = new Zapalczywy ;

raptus -> m szOstatnieSłowa = "A jednak miałem rację!" ; Mściwy `odwet = new Mściwy ;

odwet -> m nLiczbaTrafień = 13 ; Pojedynek *finał ;

finał = raptus ;

delete finał ; // wersja klasy Zapalczywy finał = odwet ;

delete finał ; // wersja klasy Mściwy

/1.6. Ident~flkacja obiektów 239

Usunięcie obiektu klasy pochodnej powoduje wykonanie najpierw destruktora klasy pochodnej, a potem destruktora klasy bazowej. Toteż efektem dwukrotnego wykonania wyrażenia:

delete finał ; będzie:

A jednak miałem rację! Pojedynek zakończony. Trafiłem go 13 razy! Pojedynek zakończony.

0x01 graphic



Wyszukiwarka

Podobne podstrony:
jcp, PLIK8
jcp, PLIK7
jcp, PLIK2
jcp, ST
jcp, ST
plik11 2U53OPXCLZ4ONZ4RFUMARPRSR22WHUK2UOD653A
jcp, PLIK12
jcp, PLIK4
jcp, PLIK13
jcp, PLIK6
JCP

więcej podobnych podstron