2.4 Dziedziczenie Poprzednie dwa rozdziały które dotyczyły zagadnienia automatów komórkowych na przykładach programów w C++. Mogłyby one sugerować że niekoniecznie trzeba programować obiektowo aby napisać program np. na grę komputerową. Dlatego powoli będziemy wprowadzać na kolejnych przykładach nowe możliwości definiowania klas. Jedną z takich możliwości jest własność dziedziczenia klas. Wyobraźmy sobie że definiujemy najpierw klasę bazową (rodzicielską) definiującą pewne ogólne własności zagadnienia a dopiero potem klasy pochodne dziedziczące pewne cechy po klasie bazowej. Na przykład w kodzie programu poniżej mamy dwie różne klasy bazowe figura i PaletaKolorow i jedną klasę pochodną kwadrat. W klasie figura są tylko ogólne cechy figury: liczba punktów N i liczba boków Nbokow. Klasa która po niej dziedziczy to klasa kwadrat i ma ona bezpośredni dostęp do pól protected klasy figura i pól publicznych ale nie ma dostępu do pól private. Do pól prywatnych dostęp jest wyłącznie poprzez metody z sekcji publicznej. Pola protected są traktowane jak pola private dla obiektów innych klas które nie dziedziczą. Poniżej mamy wydruk kodu programu figury.cc z komentarzami. W poniższym programie zauwazymy trzeci rodzaj praw dostępu do pól klasy, tzw. pola protected. Mają one sens pól prywatnych ale dostepnych dla klas pochodnych tj. dziedziczących po klasie bazowej. 1 include iostream > 2 3 using namespace std ; 4 5 class f i g u r a // k l a s a bazowa ( r o d z i c i e l s k a ) 6 protected : // t u t a j s e k c j a p r o t e c t e d tak jak prywatna 7 // a l e d l a k l a s y k t o r a d z i e d z i c z y po k l a s i e f i g u r a j e s t dostepna 8 int N; // l i c z b a wezlow 9 private : // t u a j nawet k l a s a k t o r a d z i e d z i c z y po k l a s i e f i g u r a nie ma 10 // wstepu i n a c z e j 11 // n i z poprzez metody z s e k c j i p u b l i c z n e j 12 int Nbokow ; // l i c z b a bokow 13 public : 14 f i g u r a ( int n ) N=n ; Nbokow=n ; } // k o n s t r u k t o r 15 f i g u r a ( ) } // d e s t r u k t o r 16 17 int LiczbaWezlow ( ) return N; } // zwraca l i c z b e wezlow f i g u r y 18 19 void PokazNaEkranieWartoscZmiennejNbokow ( ) 20 cout Liczab bokow Nbokow= Nbokow endl ; // pokazuje na e k r a n i e 21 // l i c z b e bokow 22 } 23 24 25 } ; //pamietamy o s r e d n i k u 26 27 class PaletaKolorow // t u t a j k l a s a zawiera t y l k o pola p u b l i c z n e 28 protected : 29 30 int k o l o r ; // dostep t y l k o d l a k l a s d z i e d z i c z a c y c h po k l a s i e 31 // PaletaKolorow 32 33 public : 34 void WpiszKolor ( ) 35 36 cout Wybierz liczbe calkowita sposrod Bialy (0), Czerwony (1), Niebieski (2). ; M R Dudek dla < OW K > http://www owk if uz zgora pl 45 Materiał dystrybuowany bezpłatnie
37 cin>>k o l o r ; // zmienna k o l o r j e s t j u z zdefiniowana w k l a s i e PaletaKolorow 38 39 while ( kolor 0 k o l o r >2) 40 cout Blad...Wybierz ponownie ; 41 cout liczbe calkowita sposrod Bialy (0), Czerwony (1), Niebieski (2). ; 42 cin>>k o l o r ; // zmienna k o l o r j e s t j u z zdefiniowana w k l a s i e PaletaKolorow 43 44 } 45 } 46 47 void PodajJakiKolor ( ) 48 cout Kolor kwadratu to kolor kolor endl ; 49 } 50 51 } ; //pamietamy o s r e d n i k u 52 53 class kwadrat : public f i g u r a, public PaletaKolorow //po dwukropku o d d z i e l o n e 54 // przecinkami nazwy k l a s po k t o r y c h 55 // k l a s a kwadrat d z i e d z i c z y, t u t a j po k l a s i e f i g u r a i 56 //po k l a s i e PaletaKolorow 57 // slowo p u b l i c oznacza ze k l a s a kwadrat nie ma dostepu 58 //do prywatnych skladowych 59 // k l a s f i g u r a i PaletaKolorow 60 //Ma natomiast dostep do p o l typu p r o t e c t e d 61 62 63 public : 64 65 kwadrat ( int k ) : f i g u r a ( k ), PaletaKolorow () } // k o n s t r u k t o r k l a s y kwadrat 66 // musi uwzglednic k o n s t r u k t o r y obu k l a s a l e 67 // poniewaz w przypadku k l a s y PaletaKolorow 68 // k o n s t r u k t o r j e s t domyslny to nie musimy go p i s a c 69 // i mozna b y l o b y z o s t a w i c kwadrat ( i n t k ) : f i g u r a ( k ) } 70 71 void PokazNaEkranieWartoscZmiennejN ( ) 72 cout Liczba wezlow N= N endl ; //mamy dostep do pola N 73 // z k l a s y f i g u r a 74 } 75 76 } ; 77 78 int main ( ) 79 kwadrat A( 4 ) ; // wartosc 4 b e d z i e oznacza 4 wezly 80 81 A. WpiszKolor ( ) ; // wpisujemy k o l o r kwadratu poprzez metode z 82 // k l a s y PaletaKolorow 83 84 A. PodajJakiKolor ( ) ; // metoda z k l a s y PaletaKolorow 85 86 A. PokazNaEkranieWartoscZmiennejN ( ) ; // t u t a j dostep do pola p r o t e c t e d 87 // j e s t mozliwy 88 A. PokazNaEkranieWartoscZmiennejNbokow ( ) ; // t u t a j dostep do pola p r i v a t e 89 // t y l k o p r z e z 90 // metode p u b l i c z n a z k l a s y f i g u r a 91 return 0 ; 92 } M R Dudek dla < OW K > http://www owk if uz zgora pl 46 Materiał dystrybuowany bezpłatnie
Kolejny przykład pokazuje że korzystanie z dziedziczenia klas może powodować różnego rodzaju błędy np. błędy jednoznaczności przy wywoływaniu metod. Komentarze w poniższym programie dziedziczenie konflikt.cc powinny pomóc zrozumieć te niuanse. 1 2 // p r z y k l a d z d z i e d z i c z e n i e m z dwoch k l a s bazowych 3 // Bazowa1 i Bazowa2 4 // Przyklad z k o n f l i k t e m przy uzyciu t e j samej nazwy metody 5 include iostream > 6 include c s t d l i b > 7 8 using namespace std ; 9 10 // 11 //DEFINICJA KLASY Bazowa1 12 // 13 class Bazowa1 14 15 protected : // p o l e dostepne rowniez d l a metod z k l a s pochodnych 16 char znak ; 17 public : 18 19 double a ; 20 Bazowa1 ( char z ) znak= z ; } 21 22 void wypisz znak ( ) cout Bazowa1: znak endl ; } 23 24 } ; // nie zapominamy o s r e d n i k u 25 // 26 // Koniec d e f i n i c j i d l a k l a s y Bazowa1 27 // 28 29 30 class Bazowa2 31 32 protected : // p o l e dostepne rowniez d l a metod z k l a s pochodnych 33 char znak ; 34 public : 35 36 double a ; 37 Bazowa2 ( double aa ) 38 a=aa ; 39 znak= x ; 40 } 41 42 void wypisz znak ( ) cout Bazowa2: znak t a endl ; } 43 44 } ; 45 // 46 // Koniec d e f i n i c j i d l a k l a s y Bazowa2 47 // 48 49 // 50 //DEFINICJA KLASY Pochodna DZIEDZICZACEJ PO KLASIE Bazowa1 i Bazowa2 51 // 52 53 class Pochodna : public Bazowa1, public Bazowa2 M R Dudek dla < OW K > http://www owk if uz zgora pl 47 Materiał dystrybuowany bezpłatnie
54 55 private : 56 int k ; 57 double b ; 58 char ZNAK; 59 public : 60 Pochodna ( char z, double q ) : Bazowa1 ( z ), Bazowa2 ( q ) 61 k=a t o i ( z ) ; // zamiana znaku na l i c z b e c a l k o w i t a 62 ZNAK= z ; 63 } 64 65 void Wypisz k ( ) // metoda 66 cout znak ZNAK t k endl ; 67 } 68 69 } ; // s r e d n i k 70 71 72 int main ( ) 73 74 char znak=new char ; // a l o k a c j a dynamiczna zmiennej znak 75 76 znak= a ; // p r z y p i s a n i e zmiennej znaku a 77 78 Pochodna R( znak, 2. 3 4 ) ; // powolujemy o b i e k t R; 79 80 R. Wypisz k ( ) ; 81 82 //R. w y p i s z z n a k ( ) ; // uruchamienie metody wypisz z n ak ( ) 83 // spowoduje komunikat b l e d u 84 85 86 R. Bazowa1 : : wypisz znak ( ) ; // t u t a j podwojnym dwukropkiem 87 // wskazujemy z j a k i e j k l a s y metoda 88 R. Bazowa2 : : wypisz znak ( ) ; 89 90 // Podobnie b l a d powoduje proba wydruku w a r t o s c i pola double d l a 91 // zmiennej a 92 93 // cout R. a endl ; 94 95 // t e r a z b e d z i e o. k. 96 cout R. Bazowa2 : : a endl ; 97 98 99 return 0 ; 100 } Konflikty wynikające z niejednoznaczności dostępu do składowych klasy nie są jedynym problemem. Zauważmy, że przy dziedziczeniu wielobazowym będzie dochodzić do niepotrzebnego wielokrotnego włączania pól klasy bazowej do klasy pochodnej. Widać to np. z poniższego diagramu. M R Dudek dla < OW K > http://www owk if uz zgora pl 48 Materiał dystrybuowany bezpłatnie
Rysunek 9: Przykład wielokrotnego włączania pól klasy bazowej. Pole X zajmuje 8 bajtów, pole Y 8 bajtów. Podwójne dziedziczenie pola X przez klasę PochodnaPochodnych powoduje, że zajmuje ona pamięć 24 bajtów. Przykładem dla tego schematu jest kod programu dziedziczwielokrot.cc gdzie do pokazania pojemności klas użyta została funkcja systemowa sizeof(). Nazwy klas są jak na schemacie. Zauważmy że pojemność klasy określają pojemności zadeklarowanych zmiennych a nie metody. 1 include iostream > 2 3 using namespace std ; 4 5 class Bazowa1 // k l a s a bazowa 1 6 7 protected : 8 double X; 9 public : 10 Bazowa1 ( ) X=1.0;} 11 double WartoscPolaX ( ) return X; } M R Dudek dla < OW K > http://www owk if uz zgora pl 49 Materiał dystrybuowany bezpłatnie
12 } ; 13 14 class Bazowa2 // k l a s a bazowa 2 15 16 protected : 17 double Y; 18 public : 19 Bazowa2 ( ) Y=2.0;} 20 double WartoscPolaY ( ) return Y; } 21 } ; 22 23 c l a s s Pochodna1 : public Bazowa1, public Bazowa2 24 // k l a s a Pochodna 1 d z i e d z i c z y po dwoch k l a s a c h Bazowa1 i Bazowa2 25 26 public : 27 double WartoscPolaX ( ) return X; } 28 double WartoscPolaY ( ) return Y; } 29 } ; 30 31 c l a s s Pochodna2 : public Bazowa1 32 // k l a s a Pochodna 2 d z i e d z i c z y po k l a s i e Bazowa1 33 34 public : 35 double WartoscPolaX ( ) return X; } 36 } ; 37 38 class PochodnaPochodnych : public Pochodna1, public Pochodna2 39 // double WartoscPolaX () return X; } //uwaga komentarz bo j e s t 40 // k o n f l i k t j e d n o z n a c z n o s c i 41 // z k t o r e g o pola odczytac wartosc X 42 double WartoscPolaY ( ) return Y; } // t u t a j nie ma k o n f l u k t u 43 44 } ; 45 46 int main ( ) 47 48 Bazowa1 B1 ; // o b i e k t Bazowa1 ; 49 Bazowa2 B2 ; // o b i e k t Bazowa2 50 51 cout Obiekt B1 zajmuje sizeof ( Bazowa1) bajtow pamieci endl ; 52 // metody n i e zajmuja pamieci 53 // moze wiec byc i c h duzo 54 cout Obiekt B2 zajmuje sizeof ( Bazowa2) bajtow pamieci endl ; 55 56 PochodnaPochodnych A; // o b i e k t A d z i e d z i c z y dwukrotnie p o l e X 57 // i raz p o l e Y 58 cout Obiekt A zajmuje sizeof ( PochodnaPochodnych) bajtow pamieci endl ; 59 60 61 return 0 ; 62 } Aby uniknąć powtarzania włączania pól klasy bazowej do klas pochodnych wprowadzone zostało dziedziczenie wirtualne. Pola klasy wirtualnej są włączane do klasy pochodnej jeden raz - nie ma konfliktu. To że klasa dziedziczona jest wirtualnie zaznacza się słowem kluczowym virtual. Poniższy program pokazuje brak konfliktu w dostępie do tych samych składowych klasy jeśli M R Dudek dla < OW K > http://www owk if uz zgora pl 50 Materiał dystrybuowany bezpłatnie
klasa po której się dziedziczy jest włączana jako klasa wirtualna. 1 include iostream > 2 3 using namespace std ; 4 5 class Bazowa1 // k l a s a bazowa 1 6 7 protected : 8 double X; 9 public : 10 Bazowa1 ( ) X=1.0;} 11 double WartoscPolaX ( ) return X; } 12 } ; 13 14 class Bazowa2 // k l a s a bazowa 2 15 16 protected : 17 double Y; 18 public : 19 Bazowa2 ( ) Y=2.0;} 20 double WartoscPolaY ( ) return Y; } 21 } ; 22 23 c l a s s Pochodna1 : virtual public Bazowa1, public Bazowa2 24 // k l a s a Pochodna 1 d z i e d z i c z y po dwoch k l a s a c h Bazowa1 i Bazowa2 25 // t u t a j dostep do k l a s y Bazowa1 jako do k l a s y w i r t u a l n e j pojawia 26 // s i e slowo kluczowe v i r t u a l 27 28 public : 29 double WartoscPolaX ( ) return X; } 30 double WartoscPolaY ( ) return Y; } 31 } ; 32 33 c l a s s Pochodna2 : virtual public Bazowa1 34 // k l a s a Pochodna 2 d z i e d z i c z y po k l a s i e Bazowa1 35 // t u t a j dostep do k l a s y Bazowa1 jako do k l a s y w i r t u a l n e j 36 37 38 public : 39 double WartoscPolaX ( ) return X; } 40 } ; 41 42 class PochodnaPochodnych : public Pochodna1, public Pochodna2 43 double WartoscPolaX ( ) return X; } //uwaga NIE MA k o n f l i k t u 44 // j e d n o z n a c z n o s c i 45 // z k t o r e g o pola odczytac wartosc X 46 double WartoscPolaY ( ) return Y; } // t u t a j nie ma k o n f l u k t u 47 48 } ; 49 50 int main ( ) 51 52 53 Bazowa1 B1 ; // o b i e k t Bazowa1 ; 54 Bazowa2 B2 ; // o b i e k t Bazowa2 55 M R Dudek dla < OW K > http://www owk if uz zgora pl 51 Materiał dystrybuowany bezpłatnie
56 cout Obiekt B1 zajmuje sizeof ( Bazowa1) bajtow pamieci endl ; 57 // metody n i e zajmuja pamieci 58 // moze wiec byc i c h duzo 59 cout Obiekt B2 zajmuje sizeof ( Bazowa2) bajtow pamieci endl ; 60 61 PochodnaPochodnych A; // o b i e k t A d z i e d z i c z y dwukrotnie p o l e X 62 // i raz p o l e Y a l e nie ma k o n f l i k t u z 63 // uzyciem metody WartoscPolaX ( ) 64 cout Obiekt A zajmuje sizeof ( PochodnaPochodnych) bajtow pamieci endl ; 65 66 67 return 0 ; 68 } Na zakończenie tego rozdziału podamy jeszcze jeden przykład ale tym razem z funkcjami wirtualnymi. Funkcje wirtualne muszą być tego samego typu i mięć tą samą liczbe i typ argumentów. Klasa w której jest co najmniej jedna funkcja wirtualna jest klasą abstrakcyjną. Dla klasy abstrakcyjnej nie można tworzyć obiektów. Poniżej jest przykład programu z klasą abstrakcyjną. 1 include iostream > 2 3 using namespace std ; 4 5 // 6 // Class name : Klasa Zwierz j e s t k l a s a a b s t r a k c y j n a 7 // to znaczy ze nie mozna tworzyc obiektow 8 // t e j k l a s y, wprowadza s i e ja po to aby d e f i n i o w a c 9 // wspolne metody d l a k l a s pochodnych 10 // D e s c r i p t i o n : 11 // 12 class Zwierz 13 public : 14 virtual void DajGlos ()=0; // oznacza to ze nie i s t n i e j e 15 // t r e s c metody w k l a s i e bazowej Zwierz 16 } ; 17 18 19 // 20 // Class name : Kot 21 // 22 // D e s c r i p t i o n : d z i e d z i c z y po k l a s i e Zwierz 23 // 24 class Kot : public Zwierz 25 public : 26 27 void DajGlos ( ) 28 cout Miau... endl ; 29 } 30 } ; 31 32 33 // 34 // Class name : Pies 35 // 36 // D e s c r i p t i o n : d z i e d z i c z y po k l a s i e Zwierz 37 // M R Dudek dla < OW K > http://www owk if uz zgora pl 52 Materiał dystrybuowany bezpłatnie
38 class Pies : public Zwierz 39 public : 40 41 void DajGlos ( ) 42 cout Hau, hau... endl ; 43 } 44 } ; 45 46 47 // 48 // Class name : Pchla 49 // 50 // D e s c r i p t i o n : d z i e d z i c z y po k l a s i e Zwierz 51 // 52 class Pchla : public Kot, public Pies 53 public : 54 55 void DajGlos ( ) 56 cout bez, bez... endl ; 57 } 58 } ; 59 60 int main ( ) 61 62 Kot Mruczek ; 63 Mruczek. DajGlos ( ) ; 64 65 Pies Latek ; 66 Latek. DajGlos ( ) ; 67 68 Pchla Cyrkowka ; 69 Cyrkowka. DajGlos ( ) ; 70 71 return 0 ; 72 } M R Dudek dla < OW K > http://www owk if uz zgora pl 53 Materiał dystrybuowany bezpłatnie