Wykład V - semestr II Kierunek Informatyka Wydział Matematyki Stosowanej Politechniki Śląskiej Gliwice, 2014 c Copyright 2014 Janusz Słupik
Programowanie obiektowe Dziedziczenie (inheritance) - mechanizm tworzenia nowych klas na podstawie już istniejących. Dziedziczenie ułatwia programiście wyrażanie wspólnych cech klas. Daje również możliwość rozszerzenia działania istniejących klas. Polimorfizm (polymorphism, wielopostaciowość) - mechanizm operowania obiektami, abstrahując od ich typu.
Dziedziczenie Klasę na podstawie, której tworzymy nową klasę nazywamy klasą podstawową (bazową, nadklasą, base class). Nową klasę, która powstała na podstawie klasy podstawowej nazywamy klasą pochodną (podklasą, derived class). class KlasaPochodna : public KlasaPodstawowa... Każda klasa pochodna może być klasą podstawową innych klas. Składowe klasy pochodnej nie mają pozwolenia na sięganie do prywatnych składowych swojej klasy podstawowej.
Przykład - część I #include <iostream> #include <string> using namespace std; class Osoba Osoba( void ) cout << "Powstaje osoba" << endl; ~Osoba( void ) cout << "Ginie osoba" << endl; string imie; string nazwisko; int wiek;
Przykład - część II class Pracownik : public Osoba Pracownik( void ) cout << "Powstaje pracownik" << endl; ~Pracownik( void ) cout << "Ginie pracownik" << endl; void ktoto( void ) cout << imie << " " << nazwisko << endl; string stanowisko;
Przykład - część III int main() Pracownik o; o.imie = "Jan"; o.nazwisko = "Kowalski"; o.wiek = 25; o.stanowisko = "kasjer"; o.ktoto(); return 0; Powstaje osoba Powstaje pracownik Jan Kowalski Ginie pracownik Ginie osoba
Dalsza rozbudowa przykładu class Klient : public Osoba string adres; string telefon; class Tymczasowy : public Pracownik int ilosc_dni_do_konca_umowy;
Hierarchia klas Osoba Pracownik Klient Tymczasowy
Operator = Pracownik p; p.imie = "Adam"; p.nazwisko = "Nowak"; Osoba o; o = p; //domyślny operator = to akceptuje //wycinanie - kopia części klasy p = o; //błąd, //brak definicji odpowiedniego operatora =
Konstruktor kopiujący a dziedziczenie W sytuacji: klasy bazowa A ma zdefiniowany konstruktor kopiujący, a jej klasa pochodna B nie ma Zachodzi: Domyślny konstruktor kopiujący klasy B kopiujący składowa po składowej, dla części odziedziczonej po klasie bazowej A wykorzysta zdefiniowany dla niej konstruktor dla kopiowania tej części.
Wywołania konstruktorów class A int a; A( void ) a = 1; A( int x ) a = x; class B : public A int b; B( void ): A( 5 ) b = 1; B( int x ): A() b = x;
Operator zasięgu :: class A A ( void ) b = 1; int b; class B : public A B ( void ) b = 2; void czytaj( void ) A::b = 3; cout << A::b << endl; int b; int main() B o; cout << o.b << endl; // zwraca 2 o.b = 7; o.czytaj(); // zwraca 3 cout << o.b << endl; // zwraca 7...
Zakrywanie - część I class A void f( void ) cout << "f z A"; cout << endl; void f( double x ) cout << "f z A ma "; cout << x << endl; class B : public A int main() B o; o.f(); o.f( 1.3 );...
Zakrywanie - część II class A // bez mian class B : public A // zakrywanie void f( double x ) cout << "f z B ma "; cout << x+5 << endl; int main() B o; // o.f(); - błędne o.f( 1.3 );... Definicja f w B zakrywa wszystkie implementacje z A.
Zakrywanie - część III class A // bez mian class B : public A using A::f; // nadpisanie void f( double x ) cout << "f z B ma "; cout << x+5 << endl; int main() B o; o.f(); o.f( 1.3 );... using A::f; uwidacznia w B wszystkie wersje metody f z A.
Przekazanie wskaźnika do obiektu class A void f( void ) cout << "f z A" << endl; class B : public A void f( void ) cout << "f z B" << endl; int main() A oa; B ob; oa.f(); // f z A ob.f(); // f z B A* p = &ob; p->f(); // f z A... Wiązanie statyczne (static binding) - określenie przez kompilator, która wersja metody f ma być wywoływana.
Funkcje wirtualne class A virtual void f( void ) cout << "f z A" << endl; class B : public A void f( void ) cout << "f z B" << endl; int main() A oa; B ob; oa.f(); // f z A ob.f(); // f z B A* p = &ob; p->f(); // f z B p = &oa; p->f(); // f z A... Wiązanie dynamiczne (inaczej późne, dynamic binding) - określenie wersji metody f dopiero podczas pracy programu, na podstawie wskazywanego obiektu.
Funkcje wirtualne - Wiązanie dynamiczne niesie ze sobą niewielki narzut pamięciowo-czasowy. - Słowo virtual może wystąpić tylko raz w klasie podstawowej i nie musi się już powtarzać przy analogicznych funkcjach w klasach pochodnych (choć może). - Funkcje wirtualne nie mogą być funkcjami inline. - Nie powtarzamy słowa virtual w definicjach funkcji poza ciałem klasy class A virtual void f( int ); void A::f( int x ) /*... */
Konstruktor, destruktor a wirtualność Konstruktor nie może być wirtualny. Powołując do życia obiekt, wiemy jakiego ma być typu. Destruktor może być wirtualny. Często zachodzi konieczność stosowania destruktorów wirtualnych. Wirtualność destruktora jest możliwa mimo, iż w klasie pochodnej ma on inną nazwę niż w klasie podstawowej. Jest to wyjątek. Zwykłe funkcje mogą być wirtualne tylko wtedy, gdy mają identyczne nazwy i argumenty.
Przykład I - wirtualny destruktor class Pojemnik ~Pojemnik( void ) cout << "Niszcze pojemnnik" << endl; class Para : public Pojemnik int *p; Para( void ) p = new int[2]; ~Para( void ) cout << "Niszcze pare" << endl; delete [] p;... int main() Pojemnik *w = new Para(); //w->... używamy obiektu delete w;... Wynik: Niszcze pojemnik
Przykład II - wirtualny destruktor class Pojemnik virtual ~Pojemnik( void ) cout << "Niszcze pojemnnik" << endl; class Para : public Pojemnik int *p; Para( void ) p = new int[2]; ~Para( void ) cout << "Niszcze pare" << endl; delete [] p;... int main() Pojemnik *w = new Para(); //w->... używamy obiektu delete w;... Wynik: Niszcze pare Niszcze pojemnik
Słowo protected Dostęp do pól i metod składowych: Klasa podstawowa Klasa pochodna Funkcja zewnętrzna public + + + protected + + - private + - -
Przykład class A int c; protected: int a;... int main() B o; class B : public A B( void ) a = 13; c = 7; void geta( void ) cout << a << endl; o.geta(); //o.a = 11; - błędne o.c = 11; // ok...
C++ jako ulepszone C W języku C dla zbudowanej struktury napisaliśmy funkcję wyświetlającą jej zawartość na ekranie. void drukuj( struct budynek *wsk ); Następnie zbudowaliśmy podobną strukturę, które ma w stosunku do poprzedniej kilka dodatkowych pól. Gdy chcemy ją wyświetlić musimy napisać nową funkcję nawet jeśli nie chcemy prezentować wartości z nowych pól. void drukuj_dom( struct dom *wsk ); W języku C++ wykorzystamy dziedziczenie i polimorfizm. Wystarczy nam jedna funkcja drukująca zawartość struktury podstawowej.
Klasa abstrakcyjna Każda funkcja wirtualna albo jest zdefiniowana dla klasy, która ją deklaruje, albo jest zadeklarowana jako czysto wirtualna (pure), np. virtual int nazwafunkcji ( int arg ) = 0; Dla klasy, która deklaruje co najmniej jedną funkcję czysto wirtualną nie można określić w programie obiektów tej klasy. Taką klasę nazywamy abstrakcyjną. Konkretnych implementacji funkcji czysto wirtualnych dostarczają dopiero klasy pochodne. Czysta funkcja wirtualna, która nie jest zdefiniowana w klasie pochodnej, pozostaje czystą funkcją wirtualną, a więc klasa pochodna jest wówczas także klasą abstrakcyjną.
Klasa abstrakcyjna Klasa abstrakcyjna służy do zadania części interfejsu klas pochodnych niezbędnego do zaimplementowania. Założenie: chcemy operować na różnych obiektach geometrycznych na płaszczyźnie np. trójkąty, kwadraty, itp. Wyodrębniamy ich wspólny zbiór cech. class Figura virtual void przesun( double, double ) = 0; virtual double pole( void ) const = 0;
Dostęp do klas podstawowych class A /*... */ class B : public A /*... */ class C : protected A /*... */ class D : private A /*... */ - Niejawnej konwersji z B* do A* mogą dokonywać wszystkie funkcje. - Niejawnej konwersji z C* do A* mogą dokonywać metody i funkcje zaprzyjaźnione klasy C oraz metody i funkcje zaprzyjaźnione jej klas pochodnych. - Niejawnej konwersji z D* do A* mogą dokonywać jedynie metody i funkcje zaprzyjaźnione klasy D.
Operator typeid #include <typeinfo> Operator typeid służy do identyfikacji typu podczas wykonywania programu (RTTI - run-time type identification). Moja ob; Moja *p = &ob; if( typeid( *p ) == typeid( Moja ) ) cout << "Te same" << endl;
Operator typeid - przykłady #include <typeinfo>... Moja ob; double x = 1; int t[] = 1, 2, 3, 4 cout << typeid( ob ).name() << endl; //class Moja cout << typeid( x ).name() << endl; //double cout << typeid( t ).name() << endl; //int [4]
Agregacja, Kompozycja Agregację uzyskujemy poprzez definiowanie w nowej klasie pól, które są obiektami istniejących klas. Kompozycja - szczególny przypadek agregacji, kiedy obiekt składowy nie istnieje bez obiektu głównego. Przykład: Klasa Pojazd silnikowy dziedziczy po klasie Pojazd. Klasa Pojazd zawiera klasę Silnik. To jest kompozycja jeżeli założyć, że silnik nie może istnieć bez pojazdu. class A string n;... A ob; cout << ob.n.size() << endl;
Dziedziczenie wielokrotne Dziedziczenie wielokrotne - klasa pochodna wywodzi się od więcej niż jednej klasy. class A /*... */ class B /*... */ class C : public A, public B /*... */ Podczas tworzenia obiektu klasy C wywoływane są konstruktory w kolejności wskazanej w liście dziedziczenia, tzn. konstruktor A, konstruktor B i na koniec konstruktor C.
Dziedziczenie wielokrotne - wieloznaczość Wieloznaczność - sytuacja kiedy w klasie pochodnej istnieje wyrażenie odnoszące się do składnika więcej niż jednej klasy podstawowej. class A int x; class B int x; class C: public A, public B C( void ) //ok class C: public A, public B C( void ) x = 1; // błąd class C: public A, public B C( void ) A::x = 1; B::x = 4;
Bliższe pokrewieństwo - brak wieloznaczności A x B C x Odwołanie się do nazwy x w klasie D prowadzi do składowej x klasy C ze względu na bliższe pokrewieństwo. D
Dziedziczenie wirtualne - część I Rozpatrzmy następującą hierarchię klas: A B C A W obrębie obiektu klasy D występują dwukrotnie składniki odziedziczone po A. Czasem to dobrze, a czasem źle. D A A B C D
Dziedziczenie wirtualne - część II Rozpatrzmy następującą hierarchię klas: Osoba (O) Pracownik (P) Osoba (O) Kobieta (K) Pracownik żeński (PZ)
Dziedziczenie wirtualne - część III class O string imie; class P : public O void setip(string s) imie = s; string getip(void) return imie; class K : public O void setik(string s) imie = s; string getik(void) return imie; class PZ : public P, public K... int main() PZ o; o.setip( "Anna" ); o.setik( "Beata" ); cout << o.getip() << endl; cout << o.getik() << endl;...
Dziedziczenie wirtualne - część IV Osoba (O) Pracownik (P) Kobieta (K) Pracownik żeński (PZ) class P : public virtual O \*... *\ class K : public virtual O \*... *\ class PZ : public P, public K \*... *\
Dziedziczenie wirtualne - część V P O K PZ
Publiczne i prywatne dziedziczenie tej samej klasy Wirtualne dziedziczenie klasy podstawowej może odbyć się zarówno w sposób public jak i private. Te same składniki pojawią się w obiekcie klasy pochodnej tylko raz, ale w jaki sposób zostaną one potraktowane? public B A D private C Jeżeli jedno dziedziczenie wirtualne tej klasy podstawowej było publiczne, to wszystkie dziedziczenia z tej klasy też będą publiczne.
Koniec