Dziedziczenie. Literatura:

Podobne dokumenty
PARADYGMATY PROGRAMOWANIA Wykład 4

Dziedziczenie jednobazowe, poliformizm

TEMAT : KLASY DZIEDZICZENIE

Programowanie 2. Język C++. Wykład 9.

Programowanie obiektowe Wykład 6. Dariusz Wardowski. dr Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ 1/14

Wykład V. Programowanie II - semestr II Kierunek Informatyka. dr inż. Janusz Słupik. Wydział Matematyki Stosowanej Politechniki Śląskiej

Dziedziczenie. Ogólna postać dziedziczenia klas:

Języki Programowania. Prowadząca: dr inż. Hanna Zbroszczyk. tel: Konsultacje: piątek:

IMIĘ i NAZWISKO: Pytania i (przykładowe) Odpowiedzi

Programowanie 2. Język C++. Wykład 3.

Programowanie w C++ Wykład 14. Katarzyna Grzelak. 3 czerwca K.Grzelak (Wykład 14) Programowanie w C++ 1 / 27

Język C++ Programowanie obiektowe

Składnia C++ Programowanie Obiektowe Mateusz Cicheński

Zaawansowane programowanie w języku C++ Programowanie obiektowe

Programowanie współbieżne Wykład 8 Podstawy programowania obiektowego. Iwona Kochaoska

1. Które składowe klasa posiada zawsze, niezależnie od tego czy je zdefiniujemy, czy nie?

Kurs programowania. Wykład 2. Wojciech Macyna. 17 marca 2016

Programowanie w C++ Wykład 13. Katarzyna Grzelak. 4 czerwca K.Grzelak (Wykład 13) Programowanie w C++ 1 / 26

Programowanie obiektowe, wykład nr 6. Klasy i obiekty

Wstęp do Programowania 2

Wprowadzenie do programowanie obiektowego w języku C++

Zaawansowane programowanie w C++ (PCP)

Podczas dziedziczenia obiekt klasy pochodnej może być wskazywany przez wskaźnik typu klasy bazowej.

Klasa jest nowym typem danych zdefiniowanym przez użytkownika. Najprostsza klasa jest po prostu strukturą, np

C++ - dziedziczenie. C++ - dziedziczenie. C++ - dziedziczenie. C++ - dziedziczenie. C++ - dziedziczenie C++ - DZIEDZICZENIE.

Mechanizm dziedziczenia

Programowanie obiektowe w C++ Wykład 12

Składnia C++ Programowanie Obiektowe Mateusz Cicheński

Java - tablice, konstruktory, dziedziczenie i hermetyzacja

PARADYGMATY PROGRAMOWANIA Wykład 3

C++ - polimorfizm. C++ - polimorfizm. C++ - polimorfizm. C++ - polimorfizm. C++ - polimorfizm POLIMORFIZM

2. Klasy cz. 2 - Konstruktor kopiujący. Pola tworzone statycznie i dynamicznie - Funkcje zaprzyjaźnione - Składowe statyczne

Programowanie obiektowe w języku

C++ - [4-7] Polimorfizm

Szablony klas, zastosowanie szablonów w programach

Klasa dziedzicząca posiada wszystkie cechy klasy bazowej (plus swoje własne) dodawanie nowego kodu bez edycji (i ewentualnego wprowadzania

PARADYGMATY PROGRAMOWANIA Wykład 2

Programowanie II. Lista 3. Modyfikatory dostępu plik TKLientBanku.h

Język C++ wykład VII. uzupełnienie notatek: dr Jerzy Białkowski. Programowanie C/C++ Język C++ wykład VII. dr Jarosław Mederski. Spis.

Programowanie Obiektowe i C++

C++ - klasy. C++ - klasy. C++ - klasy. C++ - klasy. C++ - klasy INNE SPOSOBY INICJALIZACJI SKŁADOWYCH OBIEKTU

Wykład 1. Program przedmiotu. Programowanie Obiektowe (język C++) Literatura. Program przedmiotu c.d.:

Programowanie obiektowe w języku C++ dr inż. Jarosław Forenc

Wstęp do programowania obiektowego. WYKŁAD 3 Dziedziczenie Pola i funkcje statyczne Funkcje zaprzyjaźnione, this

Programowanie obiektowe i C++ dla matematyków

Programowanie obiektowe Wykład 3. Dariusz Wardowski. dr Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ 1/21

Programowanie w C++ Wykład 12. Katarzyna Grzelak. 20 maja K.Grzelak (Wykład 12) Programowanie w C++ 1 / 32

.NET Klasy, obiekty. ciąg dalszy

Informacje ogólne. Karol Trybulec p-programowanie.pl 1. 2 // cialo klasy. class osoba { string imie; string nazwisko; int wiek; int wzrost;

TEMAT : KLASY POLIMORFIZM

dr inż. Jarosław Forenc

2.4 Dziedziczenie. 2.4 Dziedziczenie Przykłady programowania w C - kurs podstawowy

Wprowadzenie w dziedziczenie. Klasa D dziedziczy klasę B: Klasa B klasa bazowa (base class), klasa D klasa pochodna (derived class).

Techniki programowania INP001002Wl rok akademicki 2018/19 semestr letni. Wykład 4. Karol Tarnowski A-1 p.

Zaawansowane programowanie w języku C++ Klasy w C++

Programowanie w C++ Wykład 12. Katarzyna Grzelak. 28 maja K.Grzelak (Wykład 12) Programowanie w C++ 1 / 27

Dziedziczenie. Streszczenie Celem wykładu jest omówienie tematyki dziedziczenia klas. Czas wykładu 45 minut.

C++ - dziedziczenie. C++ - dziedziczenie. C++ - dziedziczenie. C++ - dziedziczenie. C++ - dziedziczenie C++ - DZIEDZICZENIE.

Wykład 8: klasy cz. 4

Programowanie w C++ Wykład 11. Katarzyna Grzelak. 13 maja K.Grzelak (Wykład 11) Programowanie w C++ 1 / 30

Zaawansowane programowanie w C++ (PCP)

Wykład 1. Program przedmiotu. Programowanie (język C++) Literatura. Program przedmiotu c.d.:

Język C++ Różnice między C a C++

Techniki programowania INP001002Wl rok akademicki 2018/19 semestr letni. Wykład 3. Karol Tarnowski A-1 p.

Polimorfizm, metody wirtualne i klasy abstrakcyjne

W2 Wprowadzenie do klas C++ Klasa najważniejsze pojęcie C++. To jest mechanizm do tworzenia obiektów. Deklaracje klasy :

Klasy abstrakcyjne i interfejsy

Polimorfizm. dr Jarosław Skaruz

Wykład 5 Okna MDI i SDI, dziedziczenie

Dziedziczenie & W slajdach są materiały zapożyczone z

Język C++ wykład VI. uzupełnienie notatek: dr Jerzy Białkowski. Programowanie C/C++ Język C++ wykład VI. dr Jarosław Mederski.

Strona główna. Strona tytułowa. Programowanie. Spis treści. Sobera Jolanta Strona 1 z 26. Powrót. Full Screen. Zamknij.

Materiały do zajęć VII

Plik klasy. h deklaracje klas

Programowanie, część I

C++ - klasy. C++ - klasy. C++ - klasy. C++ - klasy. C++ - klasy INNE SPOSOBY INICJALIZACJI SKŁADOWYCH OBIEKTU

Kurs programowania. Wykład 1. Wojciech Macyna. 3 marca 2016

1. Wartość, jaką odczytuje się z obszaru przydzielonego obiektowi to: a) I - wartość b) definicja obiektu c) typ oboektu d) p - wartość

Programowanie obiektowe, wykład nr 7. Przegląd typów strukturalnych - klasy i obiekty - c.d.

Instrukcja do pracowni specjalistycznej z przedmiotu. Obiektowe programowanie aplikacji

Automatyczne tworzenie operatora = Integer2& operator=(const Integer& prawy) { zdefiniuje. Integer::operator=(ri);

Abstrakcyjny typ danych

Programowanie obiektowe Wykład 7. Dariusz Wardowski. dr Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ 1/20

Programowanie Obiektowo Zorientowane w języku C++ Klasy, pola, metody

Programowanie obiektowe w języku C++ Zarządzanie procesami. dr inż. Jarosław Forenc. Przeładowanie (przeciążanie) operatorów

Wykład I. Programowanie II - semestr II Kierunek Informatyka. dr inż. Janusz Słupik. Wydział Matematyki Stosowanej Politechniki Śląskiej

Języki i techniki programowania Ćwiczenia 3 Dziedziczenie

JĘZYKI PROGRAMOWANIA Z PROGRAMOWANIEM OBIEKTOWYM

Automatyczne tworzenie operatora = Integer2& operator=(const Integer& prawy) {

wykład IV uzupełnienie notatek: dr Jerzy Białkowski Programowanie C/C++ Język C, a C++. wykład IV dr Jarosław Mederski Spis Język C++ - wstęp

Techniki programowania INP001002Wl rok akademicki 2017/18 semestr letni. Wykład 4. Karol Tarnowski A-1 p.

Enkapsulacja, dziedziczenie, polimorfizm

Kurs programowania. Wykład 3. Wojciech Macyna. 22 marca 2019

Podstawy Programowania Obiektowego

Typy zmiennych proste i złożone. Programowanie komputerów. Tablica. Złożone typy zmiennych. Klasa. Struktura

Programowanie obiektowe i zdarzeniowe

Technologie i usługi internetowe cz. 2

Programowanie, część I

Mechanizm dziedziczenia

Do czego służą klasy?

Transkrypt:

Literatura: Slajdy do wykładu drugiego, trzeciego i czwartego. Stanley B. Lippman Josee Lajoie Podstawy języka C++. Stanley B. Lippman Istota języka C++. Bruce Eckel Thinking in C++ edycja polska. Bjarne Stroustrup Język C++. 1 class Podstawowa int priv; int publ; int prot; Podstawowa (int a) : priv (a), publ (2*a), prot (-a) void f(int); void wypisz ( ) const; W wyniku dziedziczenia w klasie Pochodna znajdą się wszystkie pola i prawie wszystkie metody z klasy Podstawowa. Dziedziczenie Jeśli w programie chcielibyśmy utworzyć klasę podobną do klasy Podstawowa, różniącą się w kilku szczegółach, wystarczy skorzystać z techniki programowania zwanej dziedziczeniem. class Pochodna : public Podstawowa Klasa Podstawowa musi być już zdefiniowana. double dodatek; Pochodna (double a, int b) : dodatek (a), Podstawowa (b) void g ( ); void wypisz ( ) const; 2 Klasa pochodna dziedziczy składowe klasy podstawowej oprócz: konstruktorów, destruktora, operatorów przypisania. W klasie pochodnej możemy: zdefiniować dodatkowe pola, zdefiniować dodatkowe metody, przedefiniować pola i metody, które istnieją w klasie podstawowej. Dostęp do składowych (przy dziedziczeniu publicznym): składowe prywatne klasy (etykieta private) dostępne są wyłącznie w metodach tej klasy oraz jej funkcjach (metodach, klasach) zaprzyjaźnionych, składowe publiczne klasy (etykieta public) dostępne są w całym programie, składowe chronione klasy (etykieta protected) dostępne są w metodach tej klasy i jej funkcjach (metodach, klasach) zaprzyjaźnionych oraz w metodach klasy pochodnej i funkcjach (metodach, klasach) zaprzyjaźnionych z klasą pochodną. Etykieta protected pozwala więc, by klasa posiadała inny interfejs wobec programu zewnętrznego, aniżeli w stosunku do jej klas pochodnych. Program zewnętrzny sięga do jej sekcji publicznej podczas, gdy klasy pochodne mogą także korzystać z metod i pól zadeklarowanych w jej części chronionej. Użycie etykiety protected nie chroni już tak skutecznie implementacji klasy, jak etykieta private. Gdyby zaszła konieczność zmodyfikowania klasy bazowej (podstawowej), wówczas należałoby również przeredagować wszystkie klasy 3 pochodne, które bezpośrednio korzystają z komponentów protected klasy bazowej. Przykład class Podstawowa int priv; int publ; int prot; Podstawowa (int a) : priv (a), publ (2*a), prot (-a) void f(int); void wypisz ( ) const; void Podstawowa::f ( ) priv=1; publ=2; prot=3; void Podstawowa::f (int liczba) priv=publ=prot=liczba; void Podstawowa::wypisz ( ) const cout<<priv<<endl <<publ<<endl <<prot<<endl; 4

class Pochodna : public Podstawowa double dodatek; Pochodna (double a, int b) : dodatek (a), Podstawowa (b) void g ( ); void wypisz ( ) const; void Pochodna::g ( ) priv=10; błąd brak dostępu publ=20; prot=30; dodatek=15.7; void Pochodna::wypisz ( ) const Podstawowa::wypisz ( ); cout<<dodatek<<endl; Podstawowa A(5); A.priv=1; A.publ=2; A.prot=3; Pochodna B(1.2, 6); B.priv=1; B.publ=2; B.prot=3; 5 Podstawowa A(5); cout<<"podstawowa A"<<endl; A.wypisz ( ); Pochodna B (12.5, 10); cout<<"pochodna B"<<endl; B.wypisz ( ); B.g( ); B.f( ); cout<<"podstawowa B"<<endl; B.Podstawowa::wypisz ( ); Wynik działania programu: Podstawowa A 5 10-5 Pochodna B 10 20-10 12.5 Podstawowa B 1 2 3 6 Ukrywanie nazw w klasie pochodnej W klasie Pochodna przedefiniowaliśmy metodę wypisz (w tym przypadku zmianie uległa treści metody), ukryliśmy tym samym w klasie Pochodna metodę wypisz klasy Podstawowa, którą można jedynie wywołać w sposób jawny, tzn. nazwę metody musimy poprzedzić nazwą klasy, czyli B.Podstawowa::wypisz ( ); Jeśli w przedefiniowywanej w klasie pochodnej metodzie, zmienimy typ zwracanej wartości lub/i argumenty metody, to również mamy do czynienia z ukrywaniem metody z klasy bazowej (podstawowej) w klasie pochodnej i tylko w sposób jawny możemy ją wywołać (tzn. j.w.). Taką samą mamy sytuację dla pól klasy. Jeśli przedefiniowujemy metody, których nazwa jest przeciążona w klasie bazowej (podstawowej), to wszystkie pozostałe wersje tej metody są automatycznie ukrywane w nowej, pochodnej klasie. 7 class Podstawowa // int publ; void f(int); // class Pochodna2 : public Podstawowa Przykłady Pochodna2 ( ) : Podstawowa (0) Pochodna2 C; C.f( ); // C.Pochodna2::f( ); C.f(10); //błąd metoda Podstawowa::f(int) została ukryta w klasie Pochodna2, można ją wywołać w sposób jawny tzn. C.Podstawowa::f(10); class Pochodna3 : public Podstawowa Pochodna3 ( ) : Podstawowa (-5) int f ( ); Pochodna3 D; int x=d.f ( ); //int x=d.pochodna3::f ( ); D.f (-5); //błąd metoda Podstawowa::f (int); jest ukryta w klasie Pochodna3 class Pochodna4 : public Podstawowa double publ; Pochodna4 ( ) : publ (0), Podstawowa (8) Pochodna4 E; cout<<e.publ; cout<<e.podstawowa::publ; 8

class Podstawowa // int publ; void f(int); // class Pochodna3bis : public Podstawowa Pochodna3bis ( ) : Podstawowa (-5) using Podstawowa::f; int f ( ); class Pochodna2bis : public Podstawowa Przykłady inaczej Pochodna2bis ( ) : Podstawowa (0) using Podstawowa::f; Pochodna2bis C; C.f( ); // C.Pochodna2bis::f( ); C.f(10); //C.Podstawowa::f(10) Pochodna3bis D; int x=d.f ( ); //int x=d.pochodna3bis::f ( ); D.f (-5); //D.Podstawowa::f (-5); 9 Konstruktor i destruktor klasy pochodnej class Test1 Test1 (int a) : a1 (a) cout<<"konstruktor Test1"<<endl; ~Test1 ( ) cout<<"destruktor Test1"<<endl; int a1; class Test2 Test2 (int b=10) : a2 (b) cout<<"konstruktor Test2"<<endl; ~Test2 ( ) cout<<"destruktor Test2"<<endl; int a2; Pisząc: Pochodna P (2); na ekranie zobaczymy: Konstruktor Test1 Konstruktor Test2 ****!!!!! Destruktor Test2 Destruktor Test1 class Pochodna : public Test1 Pochodna (int a) : t2(5), b(12), Test1(a) cout<<"****"<<endl; ~Pochodna ( ) cout<<"!!!!!"<<endl; Test2 t2; int b; 10 Kolejność wywoływania konstruktorów nie zależy od kolejności ich występowania na liście, ale od związków zachodzących między klasami. Destruktory wywoływane są w kolejności odwrotnej do wywołań konstruktorów. Konstrukcja obiektu klasy pochodnej zaczyna się od wywołania konstruktora klasy pochodnej, który wywołuje konstruktor klasy bazowej, a następnie zostaną zainicjowane pola klasy pochodnej (w przypadku pól, które są obiektami, zostaną wywołane odpowiednie konstruktory) zgodnie z kolejnością deklaracji w klasie. W klasie pochodnej bez jawnej definicji konstruktora (w tym też bez konstruktora kopiującego), konstruktor domyślny jest generowany automatycznie przez kompilator pod warunkiem, że w klasie bazowej istnieje i jest dostępny konstruktor domyślny oraz wszystkie pola klasy pochodnej mają szanse być poprawnie zainicjowanymi. Konstruktory i destruktor nie są dziedziczone. W klasie pochodnej konstruktor kopiujący jest generowany automatycznie przez kompilator, w którym kopiowanie odbywa się składowa po składowej, co w przypadku dziedziczenia oznacza uruchomienie konstruktora kopiującego klasy podstawowej, pod warunkiem, że istnieje i jest dostępny. Jeśli konstruktor kopiujący klasy podstawowej nie jest dostępny, to kompilator nie wygeneruje konstruktora kopiującego dla klasy pochodnej. W tym przypadku, o ile 11to jest konieczne, możemy konstruktor kopiujący zdefiniować samodzielnie. Operator przypisania nie jest dziedziczony. W klasie pochodnej operator przypisania jest generowany automatycznie przez kompilator, który jest przypisaniem składowa po składowej, co w przypadku dziedziczenia oznacza uruchomienie operatora przypisania klasy podstawowej pod warunkiem, że istnieje i jest dostępny. Gdy klasa podstawowa zawiera pola const lub/i pola będące referencjami, to operator przypisania nie zostanie automatycznie wygenerowany, gdyż do tych pól nie można nic przypisać. Gdy klasa podstawowa posiada prywatny operator przypisania, to kompilator również nie wygeneruje operatora przypisania dla klasy pochodnej. W tych przypadkach, jak również w przypadku, gdy automatycznie wygenerowany operator przypisania nam nie odpowiada możemy sami go zdefiniować. 12

Definicja konstruktora kopiującego i operatora przypisania dla klasy pochodnej. #include <iostream> using std::cout; class Podstawowa int numer; double kwota; Podstawowa (int a=1, double b=0) : numer (a), kwota (b) Podstawowa (const Podstawowa& wzor); //konstruktor kopiujący Podstawowa& operator= (const Podstawowa& co); //operator przypisania void wypisz() const cout<<'\n'<<numer<<" "<<kwota<<" "; Podstawowa::Podstawowa (const Podstawowa& wzor) : numer (wzor.numer+1), kwota (wzor.kwota) Podstawowa& Podstawowa::operator= (const Podstawowa& co) if (this==&co) return *this; numer=co.numer+1; kwota=co.kwota; return *this; 13 class Pochodna : public Podstawowa int ilosc; Pochodna (int c=10) : ilosc (c) Pochodna (const Pochodna& wzor); Pochodna & operator= (const Pochodna& co); void wypisz( ) const Podstawowa::wypisz();cout<<ilosc<<'\n'; Pochodna ::Pochodna (const Pochodna& wzor) : Podstawowa (wzor), ilosc(wzor.ilosc) Pochodna& Pochodna::operator= (const Pochodna& co) if (this==&co) return *this; Podstawowa::operator= (co); ilosc=co.ilosc+100; return *this; //konstruktor kopiujący //operator przypisania konwersja Pochodna& Podstawowa& 14 int main() Podstawowa a(2,3); Pochodna P(4), B=P; a.wypisz(); P.wypisz(); B.wypisz(); P=B; P.wypisz(); a=b; a.wypisz(); Podstawowa b=b; b.wypisz(); 2 3 1 0 4 2 0 4 3 0 104 3 0 3 0 15 Standardowe konwersje. Rzutowanie w górę. Obiekt klasy pochodnej można uznać za obiekt klasy podstawowej. Predefiniowane są następujące konwersje standardowe: wskaźnik do obiektu klasy pochodnej może być niejawnie przekształcony na wskaźnik do obiektu klasy podstawowej. Pochodna* Podstawowa* referencja obiektu klasy pochodnej może być niejawnie przekształcona na referencję do obiektu klasy podstawowej. Pochodna& Podstawowa& Poprawne są więc linie: Pochodna P; Podstawowa T=P; // W linii zostanie wywołany konstruktor kopiujący Podstawowa::Podstawowa (const Podstawowa&); którego argumentem jest referencja do obiektu klasy Podstawowa. W naszym przykładzie po prawej stronie znaku = znajduje się obiekt klasy Pochodna. Wykonanie konstruktora kopiującego rozpocznie się wykonaniem konwersji Pochodna& Podstawowa&. Natomiast błędem zakończy się próba kompilacji następujących linii: Podstawowa T; 16 Pochodna P=T; //błąd niemożliwa jest konwersja Podstawowa& Pochodna&

W przypadku operatora przypisania sytuacja wygląda analogicznie. Pochodna P; Podstawowa T; T=P; // W linii przypadku zostanie wywołana metoda operatorowa Podstawowa& Podstawowa::operator= (const Podstawowa&); której argumentem jest referencja do obiektu klasy Podstawowa, tu również na początku zostanie wykonana konwersja Pochodna& Podstawowa&, ponieważ po prawej stronie operatora przypisania stoi obiekt klasy Pochodna. Rozważmy następującą hierarchię klas: class Samochod int zbiornik; class Mercedes : public Samochod class Fiat : public Samochod void stacja_benzynowa (Samochod &klient) klient.zbiornik+=50; Samochod idealny; stacja_benzynowa (idealny); spalanie (&idealny); Zdefiniujemy dwie funkcje stacja_benzynowa(samochod&) i spalanie(samochod*), które mogą pracować z każdym obiektem klasy pochodzącej od klasy Samochod. void spalanie (Samochod *wsk) wsk->zbiornik-=5; Niejawne konwersje użytkownik klasy może wykonać tylko przy dziedziczeniu publicznym. Sposób dziedziczenia określamy na liście pochodzenia klasy. Mercedes m_piotra; stacja_benzynowa (m_piotra); spalanie (&m_piotra); // Mercedes& Samochod& // Mercedes* Samochod* 17 Fiat f_pawla; stacja_benzynowa (f_pawla); spalanie (&f_pawla); // Fiat& Samochod& // Fiat* Samochod* 18 Lista pochodzenia klasy Na liście pochodzenia klasy, zamiast public (wykorzystywane najczęściej) mogą pojawić się etykiety private, protected. Etykiety określają w jaki sposób mają być traktowane składowe nieprywatne (składowe public i protected) klasy podstawowej w klasie pochodnej. class Pochodna: public Podstawowa składowe publiczne i chronione (public i protected) klasy Podstawowa mają ten sam specyfikator dostępu w klasie Pochodna. class Pochodna: private Podstawowa składowe nieprywatne klasy Podstawowa są składowymi prywatnymi klasy Pochodna. class Pochodna: protected Podstawowa składowe nieprywatne klasy Podstawowa są składowymi chronionymi klasy Pochodna. Domyślna etykietą na liście inicjalizującej jest etykieta private. 19 W przypadku dziedziczenia prywatnego użytkownik klasy nie może korzystać z niejawnych konwersji w górę. class Samochod int zbiornik; class Dom : private Samochod void stacja_benzynowa (Samochod &klient) klient.zbiornik+=50; Dom moj_dom; //stacja_benzynowa (moj_dom); błąd stacja_benzynowa ((Samochod&) moj_dom); W przypadku dziedziczenia prywatnego możliwe są tylko jawne konwersje. 20

class Przodek int i; float f; void fun1 (int, float); long l; int fun2 (double); class Potomek : private Przodek int dodatek; Przodek::i; Przodek::fun1; Przodek::fun2; W przypadku dziedziczenia prywatnego możemy wybiórczo udostępniać składowe publiczne i chronione klasy podstawowej. W deklaracjach dostępu podajemy same nazwy składowych z klasy bazowej. Nie można zmienić rodzaju dostępu do składowych udostępnianych w klasach pochodnych. 21 Klasa może wywodzić się od więcej niż jednej klasy, mówimy wtedy o dziedziczeniu wielobazowym. class B void metoda ( ); class C void metoda ( ); class A : public B, public C Klasy podstawowe umieszczamy na liście pochodzenia klasy pochodnej poprzedzając ich nazwy odpowiednimi etykietami. Dość często w przypadku dziedziczenia wielobazowego pojawiają się błędy niejednoznaczności. Najprostszym przypadkiem takiego błędu jest próba dziedziczenia przez klasę potomną składowej o nazwie, która występuje w więcej niż jednej klasie bazowej. Pisząc obiekt.metoda ( ); spowodujemy reakcję kompilatora np. następującą [C++ Error] Member is ambiguous: 'B::metoda' and 'C::metoda' W tym przypadku należy jawnie napisać z której klasy ma być wywołana metoda tzn. obiekt.c::metoda(); lub obiekt.b::metoda( ); 22 class X int x; X( ):x(-5) cout<<"konst X"<<endl; class B : public X int y; B( ) cout<<"konst B"<<endl; class C : public X int z; C( ) cout<<"konst C"<<endl; int main( ) cout<<sizeof(obiekt)<<endl; Wynik konst X konst B konst X konst C konst A 20 class X int x; X( ):x(-5) cout<<"konst X"<<endl; class B : public X int y; B( ) cout<<"konst B"<<endl; class C : public X int z; C( ) cout<<"konst C"<<endl; int main( ) cout<<sizeof(obiekt)<<endl; class A : public B, public C int h; A( ) cout<<"konst A"<<endl; 23 class A : public B, public C int h; [C++ Error] Member is ambiguous: 'X::x' and 'X::x' 24 A( ) cout<<x<<" konst A"<<endl;

class X int x; X( ):x(-5) cout<<"konst X"<<endl; class B : virtual public X int y; B( ) cout<<"konst B"<<endl; class C : virtual public X int z; C( ) cout<<"konst C"<<endl; int main( ) cout<<sizeof(obiekt)<<endl; Wynik konst X konst B konst C -5 konst A 24 Problemy z konstruktorem w hierarchiach z wirtualnymi klasami bazowymi class X int x; X(int a) :x(a) cout<<"konst X "<<x<<endl; class B : public X int y; B( ): X(2) cout<<"konst B"<<endl; class C : public X int z; C( ): X(120) cout<<"konst C"<<endl; int main( ) cout<<sizeof(obiekt)<<endl; Wynik konst X 2 konst B konst X 120 konst C konst A 20 class A : public B, public C int h; A( ) cout<<x<<" konst A"<<endl; 25 class A : public B, public C int h; A( ) cout<<"konst A"<<endl; 26 Problemy z konstruktorem w hierarchiach z wirtualnymi klasami bazowymi class X int x; X(int a) :x(a) cout<<"konst X "<<x<<endl; class B : virtual public X int y; B( ): X(2) cout<<"konst B"<<endl; class C : virtual public X int z; C( ): X(120) cout<<"konst C"<<endl; class A : public B, public C int h; A( ): X(456) cout<<"konst A"<<endl; int main( ) cout<<sizeof(obiekt)<<endl; Wynik konst X 456 konst B konst C konst A 24 27 Polimorfizm i metody wirtualne. #include <iostream> using std::cout; using std::endl; cout<<""<<endl; cout<<"gra flet"<<endl; cout<<"gra trabka"<<endl; void muzyka (const Instrument& i) i.graj ( ); Trabka t; Flet f; muzyka (t); muzyka (f); Na ekranie zobaczymy. 28

Ten sam kod z dopisanym słowem virtual przed deklaracją metody Instrument::graj() wygeneruje inny wynik Ten sam kod z dopisanym słowem const po deklaracji metody Instrument::graj() wygeneruje wynik virtual cout<<""<<endl; cout<<"gra flet"<<endl; void muzyka (const Instrument& i) i.graj ( ); Trabka t; Flet f; muzyka (t); muzyka (f); virtual cout<<""<<endl; cout<<"gra flet"<<endl; void muzyka (const Instrument& i) i.graj ( ); Trabka t; Flet f; muzyka (t); muzyka (f); Na ekranie zobaczymy. Gra trabka Gra flet cout<<"gra trabka"<<endl; 29 [C++ Warning] Non-const function Instrument::graj() called for const object cout<<"gra trabka"<<endl; Na ekranie zobaczymy. [C++ Warning] 'Flet::graj()' hides virtual function 'Instrument::graj() const' 30 [C++ Warning] 'Trabka::graj()' hides virtual function 'Instrument::graj() const' Ten sam kod z dopisanym słowem const po deklaracji metody graj() w całej hierarchii dziedziczenia wygeneruje wynik Ten sam kod z dopisanym z argumentem przekazywanym przez wartość do funkcji muzyka virtual cout<<""<<endl; cout<<"gra flet"<<endl; void muzyka (const Instrument& i) i.graj ( ); Trabka t; Flet f; muzyka (t); muzyka (f); virtual cout<<""<<endl; cout<<"gra flet"<<endl; void muzyka (const Instrument i) i.graj ( ); Trabka t; Flet f; muzyka (t); muzyka (f); cout<<"gra trabka"<<endl; Na ekranie zobaczymy. Gra trabka Gra flet 31 cout<<"gra trabka"<<endl; Na ekranie zobaczymy. 32

Metodę zadeklarowaną ze słowem kluczowym virtual, nazywamy metodą wirtualną. Wirtualne mogą być tylko metody klasy. Wirtualny nie może być konstruktor i metody statyczne. Słowo virtual umieszczamy w treści klasy w momencie deklaracji metody, w definicji metody słowa virtual nie powtarzamy. Funkcja wirtualna w klasie podstawowej jest również wirtualna we wszystkich jej klasach pochodnych. W treści klasy pochodnej w deklaracji można powtórzyć słowo virtual, ale nie jest to konieczne. Wróćmy do treści funkcji muzyka. Linia z programu z metodami wirtualnymi i.graj ( ); produkuje różne wyniki, takie zachowanie nazywamy polimorficznym, a zjawisko polimorfizmem. Pisząc w treści funkcji main muzyka (t); // wywołujemy metodę i.trabka::graj ( ); w funkcji muzyka muzyka (f); // wywołujemy metodę i.flet::graj ( ); w funkcji muzyka Mechanizm wywołania metody jako metody wirtualnej ujawnia się tylko wtedy, gdy wywołujemy metodę wirtualną za pomocą adresu tzn. referencji lub wskaźnika do obiektu klasy podstawowej. O tym którą wersję funkcji wywołamy decyduje nie typ referencji lub wskaźnika, ale to, z czym referencja jest związana lub na co pokazuje 33 wskaźnik. Wyprowadzając nowe typy danych (np. Fortepian, Gitara) ze wspólnej klasy podstawowej (u nas Instrument), nie musimy wprowadzać jakichkolwiek zmian w funkcjach, odwołujących się do interfejsu klasy podstawowej (u nas funkcja muzyka), tak by mogły one obsługiwać nowo utworzone klasy. Domyślnie metody klas nie są wirtualne (tak jak np. w Javie) bo: (a) obiekty takich klas, są trochę większe, a programy pracujące z takimi klasami trochę wolniejsze; (b) nie można by też było stosować funkcji inline. Metody wirtualne mogą być typu inline, ale w sytuacji gdy wywołanie metody odbywa się na rzecz obiektu klasy pochodnej pokazywanego wskaźnikiem (referencją) do klasy podstawowej wtedy inline jest ignorowane. Natomiast w przypadku jawnego wywołania oraz wywołania na rzecz obiektu inline nie jest ignorowane. Klasa pochodna nie musi koniecznie definiować swojej wersji metody wirtualnej, zawsze może korzystać z wersji odziedziczonej z klasy podstawowej. Przedefiniowując metodę wirtualną w klasie pochodnej, należy pamiętać o zgodności nazwy, typów argumentów i typu wartości, o ile chcemy korzystać z mechanizmu obsługującego metody wirtualne. Jedyną akceptowalną zmianą jest zastąpienie typu wartości metody wirtualnej, typem, który niejawnie daje się przekonwertować na typ pojawiający się w pierwszej deklaracji funkcji wirtualnej w hierarchii dziedziczenia, dotyczy to tylko typów definiowanych przez użytkownika. 34 class Moja virtual Moja& funkcja ( ) return *this; class Pochodna : public Moja Pochodna& funkcja ( ) return *this; class Pochodna2 : public Moja Moja& funkcja ( ) return *this; rzutowanie w górę Problemy mogą się pojawić w momencie wykorzystywania zwracanej wartości, mianowicie: Pochodna a; Pochodna c=a.funkcja( ); Pochodna2 A; // Pochodna2 C=A.funkcja( ); Pochodna2 C=dynamic_cast<Pochodna2&>(A.funkcja( 35)); Typ statyczny wskaźnika (referencji) określa sposób dostępu do metody wirtualnej. W określaniu dostępu typ dynamiczny wskaźnika (referencji) nie jest brany pod uwagę. class Moja virtual void funkcja ( ); class Pochodna1 : public Moja void funkcja ( ); class Pochodna2 : public Pochodna1 void funkcja ( ); Moja *M=new Pochodna1; M->funkcja ( ); // dostęp publiczny do Pochodna1::funkcja () Moja *N=new Pochodna2; N->funkcja ( ); // dostęp publiczny do Pochodna2::funkcja () Pochodna1 *P=new Pochodna2; P->funkcja ( ); //błąd dostęp chroniony do Pochodna2::funkcja () 36

Klasa zawierająca metody wirtualne powinna zawierać destruktor wirtualny. #include <iostream> using std::cout; using std::endl; class Moja virtual void funkcja ( ) cout<<"moja "; virtual ~Moja ( ) cout<<"destr.m. <<endl; Moja *M=new Pochodna1; M->funkcja ( ); Moja *N=new Pochodna2; N->funkcja ( ); delete M; delete N; Klasyczne metody od metod wirtualnych różnią się sposobem wiązania tzn. łączenia wywołania metody z jej treścią, tzn.: w klasycznych metodach już na etapie kompilacji odbywa się powiązanie wywołań metod z adresem określającym, gdzie te metody się znajdują, natomiast w przypadku metod wirtualnych, kompilator generuje kod dzięki któremu decyzja o powiązaniu wywołania metody z określoną wersją metody wirtualnej jest podejmowana na etapie wykonania programu, tak więc mamy dwa sposoby wiązania: wczesne wiązanie na etapie kompilacji późne wiązanie na etapie wykonania programu. class Pochodna1 : public Moja void funkcja ( ) cout<<"pochodna1 "; ~Pochodna1 ( ) cout<<"destr.p1. <<endl; class Pochodna2 : public Pochodna1 void funkcja ( ) cout<<"pochodna2 "; ~Pochodna2 ( ) cout<<"destr.p2. <<endl; Pochodna1 Pochodna2 destr.p1. destr.m. destr.p2. destr.p1. destr.m. 37 class Moja virtual void funkcja ( ); Moja A; A.funkcja ( ); //. Moja *wsk; //.. wsk->funkcja ( ); wsk->moja::funkcja ( ); 38 W jaki sposób język C++ realizuje późne wiązanie? Typowy kompilator, dla każdej klasy zawierającej metody wirtualne tworzy pojedynczą tablicę VTABLE. W tablicy VTABLE umieszczane są adresy metod wirtualnych klasy. W każdym obiekcie reprezentującym klasę posiadającą metody wirtualne, niejawnie umieszczany jest wskaźnik wirtualny VPTR wskazujący na tablicę VTABLE tej klasy. Wszystkie obiekty klasy podstawowej, a także jej klas pochodnych, posiadają wskaźnik VPTR na tym samym miejscu (często na początku obiektu). Wszystkie adresy metod wirtualnych zawartych w tablicy VTABLE są ułożone w tym samym porządku, niezależnie od konkretnego typu obiektu. Wskaźnik VPTR jest inicjowany niejawnie w konstruktorze klasy. W momencie wywołania metody wirtualnej za pomocą wskaźnika (referencji) do obiektów klasy podstawowej, kompilator niejawnie wstawia kod, pobierający wskaźnik VPTR i odnajdujący adres metody wirtualnej w tablicy VTABLE, co umożliwia wywołanie odpowiedniej metody w trakcie pracy programu. To wszystko dla nas wykonuje kompilator. Wniosek Jeśli class Moja int a; virtual void funkcja ( ); class MOJA int a; void funkcja ( ); to sizeof (Moja) >sizeof (MOJA). 39 Przykład nieco dłuższy #include <iostream> #include <string> using std::cout; using std::endl; using std::string; virtual cout<<""<<endl; virtual string co ( ) const return "Instrument"; virtual void napraw ( ) virtual ~Instrument( ) cout<<":-("; cout<<"gra flet"<<endl; string co ( ) const return "Flet"; void napraw ( ) cout<<"naprawiam flet"<<endl; class Flet_prosty : public Flet cout<<"gra flet prosty"<<endl; string co ( ) const return "Flet prosty"; cout<<"gra trabka"<<endl; string co ( ) const return "Trabka"; void napraw ( ) cout<<"naprawiam trabke"<<endl; 40

Instrument *A[ ]=new Trabka, new Flet, new Flet_prosty for (int i=0; i<sizeof (A)/sizeof (*A) ; ++i) A[i]->graj ( ); for (int i=0; i<sizeof (A)/sizeof (*A) ; ++i) A[i]->napraw ( ); for (int i=0; i<sizeof (A)/sizeof (*A) ; ++i) delete A[i]; Na ekranie zobaczymy Gra trabka Gra flet Gra flet prosty Naprawiam trabke Naprawiam flet Naprawiam flet :-(:-(:-( 41 Przykład. Klasa abstrakcyjna. virtual =0; virtual string co ( ) const = 0; virtual void napraw ( ) =0; virtual ~Instrument( ) =0; cout<<"gra flet"<<endl; string co ( ) const return "Flet"; void napraw ( ) cout<<"naprawiam flet"<<endl; class Flet_prosty : public Flet cout<<"gra flet prosty"<<endl; string co ( ) const return "Flet prosty"; cout<<"gra trabka"<<endl; string co ( ) const return "Trabka"; void napraw ( ) cout<<"naprawiam trabke"<<endl; void Instrument::graj ( ) const cout<<""<<endl; string Instrument::co ( ) const Program z poprzedniego slajdu będzie return "Instrument"; 42 działał identycznie. Instrument ::~Instrument( ) cout<<":-("; Abstrakcyjne klasy podstawowe i metody czysto wirtualne. Klasa abstrakcyjna to klasa, która nie reprezentuje, żadnego konkretnego obiektu (np. klasa Ssak, klasa Figura, klasa Instrument). Metoda czysto wirtualna, to funkcja która oprócz słowa kluczowego virtual posiada adnotacje =0 na końcu deklaracji. Klasa z metodami czysto wirtualnymi jest klasą abstrakcyjną. Kompilator nie pozwoli nikomu na utworzenie obiektu takiej klasy, gdyż klasa abstrakcyjna to jakby niedokończona klasa. Dokończenie jej jest realizowane przez jej klasy pochodne. Klasy abstrakcyjne określają wspólny interfejs dla klas pochodnych. Klasy abstrakcyjne wprowadza się po to żeby je dziedziczyć. 43