Podstawy programowania II Zachodniopomorska Szkoła Biznesu Podstawy programowania II Wykład 7: Dziedziczenie
Kiedy przydatne jest dziedziczenie? Wyobraźmy sobie, że mamy napisaną świetnie działającą klasę Osoba służącą do obsługi danych osobowych. Chcemy teraz napisać klasę Student służącą do przetwarzania danych studenta uczelni (w tym danych osobowych). Naturalną myślą jest pytanie o możliwość wykorzystania gotowej klasy Osoba do obsługi danych osobowych studenta, co pozwoli ograniczyć się do oprogramowania właściwości klasy Student niezwiązanych z danymi osobowymi. W takiej sytuacji z pomocą przychodzi mechanizm dziedziczenia, pozwalający przy tworzeniu klasy, zwanej potomną, wykorzystać właściwości i metody innej, wcześniej utworzonej klasy, zwanej bazową. Podstawy programowania II - Dziedziczenie
Podstawy programowania II - Dziedziczenie Istota dziedziczenia Dziedziczenie polega na definiowaniu klasy potomnej w oparciu o klasę bazową na zasadzie: Klasa Potomna = Klasa Bazowa+ Nowe Elementy Klasa potomna ma więc wszystkie właściwości i metody klasy bazowej oraz dodatkowe, własne właściwości i metody. Można powiedzieć, że klasa potomna jest rodzajem (lub podklasą) klasy bazowej Dziedziczenie jest możliwe nawet wtedy, jeżeli klasa bazowa napisana była przez kogoś innego i dostępna jest na przykład w postaci biblioteki i nie mamy jej pełnego kodu źródłowego!!!. Niezbędna jest tylko jej deklaracja (zwykle w pliku .H) Podstawy programowania II - Dziedziczenie
Podstawy programowania II - Dziedziczenie Dziedziczenie w C++ Składnia deklaracji klasy potomnej class Potomna : [tryb dostępu] Bazowa { // deklaracje składników klasy potomnej }; Bazowa musi być nazwą znanej wcześniej klasy tryb dostępu private (domyślnie), protected lub public Wewnątrz funkcji klasy Potomna można używać składników klasy Bazowa tak jakby były jej własnymi (z wyjątkiem składników prywatnych klasy Bazowa) Podstawy programowania II - Dziedziczenie
Przykład dziedziczenia class Bazowa { public: int baza; void wypiszBazowa() { cout << "baza=" << baza; } }; class Potomna : public Bazowa public: float dodatek; void wypiszPotomna() { wypiszBazowa(); cout << "dodatek=" << dodatek; } }; Przykład: DZIEDZICZENIE1.CPP Bazowa: baza wypiszBazowa() Potomna: baza dodatek wypiszBazowa() wypiszPotomna() Podstawy programowania II - Dziedziczenie
Dziedziczenie od kilku klas Klasa może dziedziczyć właściwości kilku klas, np. klasa Kwadrat mogłaby dziedziczyć właściwości klasy Prostokąt i klasy Romb Składnia: class Potomna : [tryb] Bazowa1, [tryb] Bazowa2 [itd.] { // deklaracje składników klasy potomnej }; Utworzenie klasy dziedziczącej od kilku klas może być wartościowe nawet gdy sama klasa nie deklaruje własnych składników. Skupia ona wtedy tylko cechy klas bazowych (np. Kwadrat) Podstawy programowania II - Dziedziczenie
Dostęp do dziedziczonych składników Dziedziczenie wymaga podania trybu dziedziczenia (private, protected lub public) Dostęp do dziedziczonych składników zależy trybu dziedziczenia oraz od ich trybów dostępu zdefiniowanych w klasie bazowej: Dla trybu dziedziczenia public, tryby dostępu: private - brak dostępu w klasie potomnej, jedyna droga to wykorzystanie odziedziczonych funkcji (ale nie prywatnych!) protected - funkcje składowe klasy potomnej mają dostęp, z zewnątrz - brak (tak, jakby były prywatne) public - dostęp zarówno w klasie, jak i z zewnątrz Dziedziczenie w trybie protected powoduje zablokowanie dostępu z zewnątrz do wszystkich składników, a tryb private - blokuje je również dla ewentualnych klas potomnych Podstawy programowania II - Dziedziczenie
Podstawy programowania II - Dziedziczenie Dostęp w trybie public class Bazowa { int prywatny; protected: int chroniony; public: int publiczny; }; class Potomna : public Bazowa { public: float dodatek; Potomna() //konstruktor { prywatny=0; // private chroniony=0; //protect. publiczny=0; // public dodatek=0; } }; void main() { Potomna obiekt; obiekt.publiczny=0; obiekt.chroniony=0; obiekt.dodatek=0; } Podstawy programowania II - Dziedziczenie
Dostęp w trybie protected class Bazowa { int prywatny; protected: int chroniony; public: int publiczny; }; class Potomna : protected Bazowa { public: float dodatek; Potomna() { prywatny=0; // private chroniony=0;// protected publiczny=0;// protected!! dodatek=0; } }; class Wnuk : public Potomna { public: Wnuk() { chroniony=1; publiczny=1; } }; void main() { Potomna obiekt; obiekt.publiczny=0; // teraz już chroniony! obiekt.chroniony=0; obiekt.dodatek=0; } Podstawy programowania II - Dziedziczenie
Dostęp w trybie private class Bazowa { int prywatny; protected: int chroniony; public: int publiczny; }; class Potomna : private Bazowa { public: float dodatek; Potomna() { prywatny=0; // private chroniony=0;// private!! publiczny=0;// private!! dodatek=0; } }; class Wnuk : public Potomna { public: Wnuk() { chroniony=1; // teraz już prywatny! publiczny=1; // teraz już prywatny! } }; Przykład: DZIEDZICZENIE2.CPP Podstawy programowania II - Dziedziczenie
Przesłanianie składników klasy bazowej W klasie potomnej można utworzyć daną lub funkcję o identycznej nazwie jak w klasie bazowej Składnik klasy bazowej jest wtedy przesłonięty, niedostępny przez swoją nazwę Dostęp do przesłoniętego składnika umożliwia operator zakresu :: class A { int x; }; class B { int x; wypisz_oba() { cout << "Bazowa: " << A::x << "Potomna: " << x; } }; Przykład: WEKTORY.CPP Podstawy programowania II - Dziedziczenie
Przesłanianie funkcji klasy bazowej Znacznie częściej przesłania się funkcje niż dane, ponieważ umożliwia to zmianę zachowania się obiektów. Jest to przydatne podczas korzystania z gotowych klas, można podmienić działanie wybranej funkcji class A { public: int x; void wypisz() { cout << x; } // ta funkcja nam się nie podoba }; class nowaA : public A { public: void wypisz() { cout << "x= " << x << endl; } // podmiana }; Podstawy programowania II - Dziedziczenie
Dziedziczenie konstruktory i destruktor Konstruktorów ani destruktorów się nie dziedziczy Konstruktor klasy potomnej musi wywołać konstruktory klas bazowych (lista inicjalizacyjna), chyba że klasy bazowe zdefiniowały konstruktory domniemane Kolejność wykonywania konstruktorów: Konstruktory klas bazowych Konstruktory obiektów - składników klasy Konstruktor klasy potomnej Przykłady: WEKTORY.CPP, FIGURY.CPP Podstawy programowania II - Dziedziczenie
Co można przypisać do obiektu? // wyrażenie tego samego typu, np. obiekt tej samej klasy Klasa obiekt1, obiekt2; obiekt1= obiekt2; // obiekt chwilowy utworzony za pomocą konstruktora obiekt1= KonstruktorKlasy(pararmetry); // obiekt klasy potomnej, z dowolnego pokolenia KlasaPotomna pot; obiekt1=pot; // skopiowane zostaną tylko odziedziczone dane // pot=obiekt1; // odwrotnie nie można przypisać // wywołanie funkcji zwracającej obiekt tej lub potomnej klasy obiekt1=funkcja(parametry); Podstawy programowania II - Dziedziczenie
Co można przypisać do wskaźnika na obiekt? // wyrażenie tego samego typu, np. obiekt tej samej klasy Klasa obiekt1, obiekt2; Klasa *wsk1, *wsk2; // pobrazny adres obiektu tej samej klasy wsk1 = &obiekt1; // inny wskaźnik do tej samej klasy wsk2=wsk1; // wskaźnik na obiekt klasy potomnej KlasaPotomna potomek; KlasaPotomna *pwsk=&potomek; wsk1=&potomek; wsk2=pwsk; // pwsk=wsk1 // odwrotnie się nie da! Podstawy programowania II - Dziedziczenie
Podstawy programowania II - Dziedziczenie Polimorfizm Mechanizm ten jest ściśle związany z dziedziczeniem i działa w sytuacji, gdy: W klasie bazowej zdefiniowano pewną funkcję jako wirtualną (virtual) Klasy potomne definiują funkcję o identycznym nagłówku jak w klasie bazowej (teoretycznie powinna zostać przesłonięta) W programie korzystamy ze wskaźnika na klasę bazową do pokazywania na obiekty zarówno klasy bazowej jak i klas potomnych W momencie wywołania funkcji wirtualnej dla obiektu pokazywanego przez ten wskaźnik, zostanie automatycznie wybrana odpowiednia wersja funkcji wirtualnej! Podstawy programowania II - Dziedziczenie
Przykład polimorfizmu class Parent { public: virtual void Hello() // funkcja wirtualna { printf("Parent!\n"); } }; class Child : public Parent // dziedziczenie public: void Hello() // tu virtual moze byc ale nie musi { printf("Child!\n"); } }; Parent par; // obiekt klasy bazowej Child ch; // obiekt klasy potomnej Parent *parent_wsk; // wskaźnik na klasę bazową parent_wsk=∥ // adres obiektu bazowego parent_wsk->Hello(); // "Parent" (nic dziwnego) parent_wsk->&ch; // adres obiektu potomnego parent_wsk->Hello(); // "Child" !!!! Podstawy programowania II - Dziedziczenie
Funkcja czysto wirtualna i klasa abstrakcyjna Czasem klasę tworzy się wyłącznie po to aby stanowiła bazę dla kilku klas potomnych, np. pewna funkcja tej klasy ma sens tylko dla klas potomnych, a nie ma sensu dla klasy bazowej Funkcję taką można utworzyć jako czysto wirtualną, tzn. niezdefiniowaną dla klasy bazowej, ale za to konieczną do zdefiniowania w klasach potomnych Klasę, która zawiera choćby jedną taką funkcję nazywa się klasą abstrakcyjną, a tworzenie obiektów tej klasy jest zabronione Przykład deklaracji: class Abstrakcyjna { public: virtual void CzystoWirtualna()=0; }; Podstawy programowania II - Dziedziczenie
Dziękuję