Różnice

Różnice między wybraną wersją a wersją aktualną.

Odnośnik do tego porównania

pl:dydaktyka:jimp2:2016:labs:dziedziczenie [2016/02/17 11:28]
127.0.0.1 edycja zewnętrzna
pl:dydaktyka:jimp2:2016:labs:dziedziczenie [2019/06/27 15:50]
Linia 1: Linia 1:
-====== Dziedziczenie i polimorfizm ====== 
- 
-===== Dziedziczenie ===== 
- 
-====Klasy podstawowe i pochodne==== 
- 
-Dziedziczenie to jeden z najważniejszych mechanizmów programowania obiektowego. Polega on na ponownym wykorzystaniu kodu w taki sposób, że nowe klasy tworzone są na podstawie już istniejących dziedzicząc jej metody i pola i jednocześnie dodając nowe metody i nowe pola. 
- 
-{{.:​dziedziczenie.png|Dziedziczenie}} 
- 
-Aby zapisać relację dziedziczenia w C%%++%% stosuje się następujący schemat: 
- 
-**class** //​NazwaKlasyPochodnej//​ **:** //Tryb dziedziczenia//​ //​NazwaKlasyBazowej//​ { //definicja klasy  pochodnej// }  
- 
-Chcąc zapisać W C%%++%% relację z rysunku, moglibyśmy to zrobić w następujący sposób: 
-<code cpp> 
-class CzlonekUczelni{...};​ 
-class Student: public CzlonekUczelni{...};​ 
-class Pracownik: public CzlonekUczelni{...};​ 
-class PracownikNaukowy : public Pracownik{...} 
-class PradownikAdministracyjny:​ public Pracownik{...};​ 
-//​Dziedziczenie wielokrotne 
-class Stażysta : public Student, PracownikNaukowy {...}; 
-</​code>​ 
- 
- 
-====Dziedziczenie składowych==== 
- 
-Dostęp do składowych klasy bazowej może być ograniczony modyfikatorami dostępu zastosowanymi w klasie bazowej jak również typem dziedziczenia. 
- 
-Poniższa tabela przedstawia zależności pomiędzy trybem dziedziczenia a tym jak zmienia sie znaczenie modyfikatorów dostępu w klasie pochodnej. 
- 
-^Modyfikator dostępu w klasie bazowej^Rodzaj dziedziczenia^^^ 
-^ ^public^protected^private^ 
-^public|//​public//​ w klasie pochodnej.|//​protected//​ w klasie pochodnej|//​private//​ w klasie pochodnej| 
-^protected|//​protected//​ w klasie pochodnej| //​protected//​ w klasie pochodnej|//​private//​ w klasie pochodnej| 
-^private|Ukryte w klasie pochodnej|Ukryte w klasie pochodnej|Ukryte w klasie pochodnej| 
- 
- 
-==== Konstruktory i destruktory==== 
- 
-Klasa pochodna tak naprawdę jest jednocześnie klasą bazową, dlatego nie można utworzyć jej obiektu bez wcześniejszego utworzenia obiektu klasy bazowej. Każdy konstruktor klasy pochodnej w pierwszej kolejności będzie wywoływał konstruktor klasy bazowej. 
- 
-{{.:​dziedziczenie-punkt.png|Dziedziczenie}} 
- 
-Konstruktora klasy bazowej nie można wywołać w ciele konstruktora klasy pochodnej, ponieważ klasy pochodne dziedziczą pola klasy podstawowej,​ a wszystkie pola klasy muszą zostać utworzone **przed** utworzeniem danej klasy. ​ 
- 
-Chcąc wywołać konstruktor klasy Punkt2D przez konstruktor klasy pochodnej Punkt3D stosujemy następujący zapis:<​code cpp>#​include "​Punkt2D.h"​ 
-#include "​Punkt3D.h"​ 
- 
-Punkt3D::​Punkt3D(double x, doubley, double _z) : Punkt2D(x,​y) { 
-z = _z; 
-} 
-</​code>​ 
- 
- 
- 
-====Metody składowe==== 
-Ponieważ każdy obiekt klasy pochodnej //jest// jednocześnie obiektem klasy bazowej nie ma żadnych różnic pomiędzy używaniem metod klasy bazowej i pochodnej w klasie pochodnej, czy tez na obiekcie klasy pochodnej. Należy jednak pamiętać o trybach dziedziczenia i ograniczeń jakie z tego wynikają. 
- 
-W przypadku gdy w klasie pochodnej zdefiniowana zostanie ponownie taka sama metoda jak w klasie bazowej, nastąpi tak zwane napisanie tej metody. Wywołując tą metodę na obiekcie klasy pochodnej będzie wywoływana metoda nowo zdefiniowana. 
- 
-Jeśli klasa bazowa ma przeciążone **operatory**, ​ nie zostaną one odziedziczone przez klasę pochodną! 
- 
-<code cpp> 
-#include <​iostream>​ 
-using namespace std; 
- 
-class LiczbaRzeczywista{ 
-  protected: 
-    double re; 
-  public: 
-    LiczbaRzeczywista(double r){re=r;} 
-    void wypisz(){ 
-      cout << re << endl; 
-    } 
-    void powitaj(){ 
-      cout << "​Czesc!"​ << endl; 
-    } 
-  
-    LiczbaRzeczywista operator+(const LiczbaRzeczywista&​ r){ 
-      LiczbaRzeczywista rr(re+r.re);​ 
-      return rr; 
-    } 
-  
-}; 
-  
-class LiczbaZespolona : public LiczbaRzeczywista{ 
-  protected: 
-    double im; 
-  public: 
-    LiczbaZespolona(double re, double i):​LiczbaRzeczywista(re){im=i;​} 
-    void wypisz(){ 
-      cout << re << " + " << im << "​i"​ << endl; 
-    } 
-}; 
-  
-int main(){ 
-  LiczbaRzeczywista a(12); 
-  LiczbaZespolona b(23,5); 
-  
-  a.powitaj(); ​        //​Czesc! 
-  a.wypisz(); ​         //12 
-  (a+a).wypisz(); ​     //24 
-  
-  b.powitaj(); ​        //​Czesc! 
-  b.wypisz(); ​         //23 + 5I 
-  
-  
-  (b+b).wypisz(); ​     //Co się wypisze? 
-}            ​ 
-</​code>​ 
- 
-===== Polimorfizm ===== 
-Polimorfizm to możliwość różnej odpowiedzi na ten sam komunikat (wywołanie tej samej metody) przez obiekty różnych klas powiązanych dziedziczeniem. ​ 
-Poniższe podsekcje składają się na opis tego mechanizmu. 
- 
-==== Funkcje wirtualne ==== 
-Podczas dziedziczenia,​ klasy pochodne mogą nadpisywać metody swoich klas bazowych. Kiedy jednak dokonujemy rzutowania w górę (np. z LiczbaUrojona na LiczbaRzeczywista),​ nadpisane metody wracają z powrotem do swoich pierwotnych postaci (np. obliczanie pola przekroju powierzchni kuli za pomocą rzutowania na koło). Takie zachowanie nie zawsze jest pożądane. 
- 
-Załóżmy, że chcemy zbudować prosty program obsługujący bazę **pracowników** pewnej firmy. Zakładamy, że firma zatrudnia pracowników na **umowę o dzieło**, lub na **umowę o pracę**; wpływa to na obliczenie pensji netto każdego pracownika. ​ 
- 
-Mamy zatem taką relację: 
- 
-{{.:​umowa.png|Umowa - diagram UML}} 
- 
-Taka relacja umożliwia stworzenie tylko jednej klasy //​Pracownik//,​ która będzie miała jedno pole //Umowa//, któremu z kolei będzie przypisywana abo //​UmowaDzielo//​ albo //​UmowaPraca//​. W takim wypadku po rzutowaniu //​UmowaDzielo//​ lub //​UmowaPraca//​ na typ bazowy //Umowa//, musi być możliwe wywołanie metod odpowiednio obliczających wynagrodzenie netto. W tym celu stosowane są **funkcje wirtualne**. 
- 
-**Funkcja wirtualna**,​ to taka metoda klasy, która nadpisana w klasie pochodnej, nawet po rzutowaniu obiektu na typ bazowy zachowa swoją implementacje. Klasa bazowa z funkcją wirtualną będzie //​polimorficzna//​ - w zależności od tego jaki obiekt klasy pochodnej był na nią rzutowany, taka implementacja metody zostanie uruchomiona. 
- 
-Przykład:<​code cpp> 
-#include <​iostream>​ 
-#include <​list>​ 
- 
-using namespace std; 
- 
-class Umowa{ 
-  protected: 
-    double wynagrodzenieBrutto;​ 
-  public: 
-    Umowa(double pensja):​wynagrodzenieBrutto(pensja){};​ 
-    virtual double pobierzNetto();​ 
-    double pobierzBrutto();​ 
-}; 
- 
-class UmowaDzielo:​ public Umowa{ 
-  public: 
-    UmowaDzielo(double pensja):​Umowa(pensja){};​ 
-    virtual double pobierzNetto();​ 
-}; 
- 
-class UmowaPraca: public Umowa{ 
-  public: 
-    UmowaPraca(double pensja):​Umowa(pensja){};​ 
-    virtual double pobierzNetto();​ 
-}; 
- 
-class Pracownik{ 
-  private: 
-    string imie,​nazwisko,​pesel;​ 
-    Umowa* umowa; 
-  public: 
-    Pracownik(string i,string n,string p,Umowa* u) 
-      :​imie(i),​nazwisko(n),​pesel(p),​umowa(u){};​ 
-    Pracownik(const Pracownik&​);​ 
-    ~Pracownik(){};​ 
-    double pobierzPensje();​ 
-    friend ostream&​ operator<<​(ostream&,​Pracownik&​);​ 
-};</​code>​ 
- 
-====Destruktory wirtualne==== 
-Zwróć uwagę, że podobnie do metod zachowują się destruktory. Jeśli obiekt jest jawnie niszczony prze operator //delete// to wywoływany jest jego destruktor:<​code cpp> 
-#include <​iostream>​ 
-... 
-int main(){ 
-  Umowa* umowa = new UmowaPraca(10000);​ 
-  
-  // zostanie wywołany destruktor klasy Umowa, a nie UmowaPraca! 
-  // Jeśli UmowaPraca alokowałaby dodatkowo jakąś pamięć 
-  // NIE ZOSTAŁABY ONA ZWOLNIONA 
-  delete umowa; 
-} 
-</​code>​ 
- 
-Rozwiązaniem jest deklarowanie destruktora jako wirtualnego:<​code cpp> 
-class Umowa{ 
-  ... 
-  virtual ~Umowa(); 
-}; 
- 
-class UmowaPraca :public Umowa{ 
-  ... 
-  virtual ~UmowaPraca();​ 
-}; 
- 
-int main(){ 
-  Umowa* umowa = new UmowaPraca(10000);​ 
-  
-  // zostanie wywołany destruktor klasy UmowaPraca 
-  delete umowa; 
-} 
-</​code>​ 
- 
-==== Klasy abstrakcyjne==== 
- 
-Czasem w klasie bazowej implementowanie jakiejś metody jest bezcelowe. W naszym przykładzie z pracownikami implementowanie metody //​pobierzNetto//​ w klasie //Umowa// nie ma sensu, bo nie wiadomo jaki jest to typ umowy. 
- 
-Metody takie można zadeklarować jako czysto wirtualne:<​code cpp>​class UmowaDzielo:​ public Umowa{ 
-  protected: 
-    double wynagrodzenieBrutto;​ 
-  public: 
-    Umowa(double pensja):​wynagrodzenieBrutto(pensja){};​ 
-    // Metoda czysta. Nie posiada implementacji w klasie bazowej. 
-    // MUSI jednak być zaimplementowana w pochodnej (lub ponownie ​ 
-    // zadeklarowana jako czysta 
-    virtual double pobierzNetto() = 0; 
-}; 
-</​code>​ 
- 
-Klasę zawierającą choć jedną metodę czystą nazywamy **klasą abstrakcyjną**. ​ 
-Nie jest możliwe utworzenie obiektu takiej klasy! Służy ona jedynie jako klasa bazowa dla innych klas. Swego rodzaju interfejs. 
- 
- 
-=====Rzutowanie===== 
-Rzutowanie to inaczej konwersja pomiędzy typami. Wyróżnia się dwa rodzaje rzutowania: 
-  * rzutowanie w górę (z klasy pochodnej na klasę bazową) 
-  * rzutowanie w dół (Z klasy bazowej na klasę pochodną) 
- 
-====Rzutowanie w górę==== 
-Rzutowanie w górę można wykonywać niejawnie, ponieważ każdy obiekt klasy pochodnej //jest// jednocześnie obiektem klasy bazowej: 
-<code cpp> 
- 
-void foore(LiczbaRZeczywista r){ 
-  cout << "​Wyswietlamrzeczywista "; 
-  r.wypisz(); 
-} 
- 
-int main(){ 
-  LiczbaZespolona z(12,4); 
- 
-  foore(z); ​ //nastąpi rzutowanie w górę 
-  ​ 
-} 
- 
-</​code>​ 
- 
-====Rzutowanie w dół==== 
- 
-Rzutowanie w dół oznacza rzutowanie z klasy bazowej na pochodną. Jeśli nie zostanie przeciążony odpowiedni operator rzutowania, operację taką należy wykonywać przy użyciu jednego z poniższych operatorów:​ 
- 
-^Operator^Opis^ 
-^static_cast<​nowy_typ>​(wyrazenie)|Sprawdzanie poprawności typów podczas rzutowania wykonywane jest podczas kompilacji| 
-^const_cast<​nowy_typ>​(wyrazenie)|Może być stosowany tylko ze wskaźnikami lub referencjami. Pozwala na anulowanie stałości wskaźnika obiektu (//​const//​).| 
-^dynamic_cast<​nowy_typ>​(wyrazenie)|Może być stosowany tylko ze wskaźnikami lub referencjami.Pozwala na rzutowanie w górę (dla wszystkich klas) i na rzutowanie w dół (tylko dla klas polimorficznych),​ sprawdzając czy obiekt który rzutujemy jest faktycznie kompletnym obiektem klasy na którą chcemy go rzutować. Operator przeprowadza ten test podczas działania programu; Jeśli konwersja nie może się odbyć, zwracany jest wskaźnik null ( w przypadku referencji rzucany jest wyjątek //​bad_cast//​)| 
-^reinterpretc_cast<​nowy_typ>​(wyrazenie)|Stosowanie tego operatora jest potencjalnie bardzo niebezpieczne. Może on rzutować dowolny typ na dowolny inny typ, nawet jeśli oba typy nie mają ze sobą nic wspólnego.| 
- 
- 
-Poniżej przedstawiono przykłady użycia operatorów i różnice w ich stosowaniu: 
-<code cpp> 
-class A { 
-  public: 
-    virtual void foo() = 0; 
-}; 
- 
-class B: public A{ 
-  public: 
-    virtual void foo(){} 
-}; 
- 
-class C { 
-  public: 
-    void bar(){}; 
-}; 
- 
-class D: public C{ 
-  public: 
-    void otherbar(){} 
-}; 
- 
-int main(){ 
-  // static_cast - zwykłe rzutowanie w górę 
-  D* ptrD = new D(); 
-  C* ptrC = static_cast<​C*>​(ptrD);​ 
- 
-  // const_cast - pozbywamy się modyfikatora const 
-  const C* constPtrC = new C(); 
-  C* ptrCC = const_cast<​C*>​(constPtrC);​ 
-  ​ 
-  // dynamic_cast - rzutowanie w dół 
-  A* ptrA = new B(); 
-  B* ptrB = dynamic_cast<​B*>​(ptrA);​ 
-  ​ 
-  // reinterpret_cast - rzutowanie pomiedzy kompletnie niepowiazanymi klasami 
-  B* ptrBB = new B(); 
-  D* ptrDD = reinterpret_cast<​D*>​(ptrBB);​ 
-} 
-</​code>​ 
- 
- 
-====== Ćwiczenia ====== 
-<WRAP center round important 60%> 
-**UWAGA**\\ 
-Przesyłając rozwiązania zadań mailowo należy zamieścić (w formie komentarza w kodzie lub w treści maila) odpowiedzi na problemy postawione w zadaniach 2-4 **wraz z uzasadnieniami**. Zadanie bez właściwego uzasadnienia nie będzie zaliczone. 
-</​WRAP>​ 
- 
-  - [1 plus] Przetestuj przykład z sekcji [[#​metody_skladowe|Metody składowe]] 
-  - **[1 punkt] Wykorzystując klasę [[.:​klasy1#​deklaracja_klasy|Punkt]],​ napisz klasę Punkt3D dziedziczącą po niej i implementującą dodatkowo metodę //double distance(Punkt3D)//​. W każdym z konstruktorów i destruktorów klas Punkt i Punkt3D wypisz na ekran jakąś wiadomość i zaobserwuj w jakiej kolejności wywołują się konstruktory i destruktory.** 
-  - **[1 punkt] Mając dwa obiekty, jeden klasy Punkt a drugi klasy Punkt3D o nazwach //punkt2d// i //​punkt3d//,​ wywołaj punkt2d.distance(punkt3d). Co sie stało?** 
-  - **[1 punkt] W klasie Punkt2D istnieje przeciążony operator wpisywania do strumienia ("<<"​). Co się stanie jeśli będziesz chciał wypisać obiekt klasy Punkt3D w następujący sposób:** <code cpp>​Punkt3D p3d(1,2,3); 
-cout << p3d << endl;</​code>​ 
-  - [3 plusy] Napisz dwie klasy: Kolo i Kula. Klasa Kolo powinna być klasą bazową dla klasy Kula. 
-    * W klasie Kolo powinny znaleźć się następujące pola i metody: 
-      * **double** x, y, r - określające odpowiednio współrzędne środka koła i jego promień. 
-      * **double** pole() - obliczająca pole koła 
-    * W klasie Kula powinny znaleźć się następujące pola i metody: 
-      * **double** z - określajaca współrzędną przestrzenną środka kuli 
-      * **double** pole() - obliczająca pole powierzchni kuli 
-    * W jaki sposób mając następującą funkcję //main// można obliczyć pole przekroju zdefiniowanej tam kuli płaszczyzną przechodzącą przez środek kuli, wywołując wyłącznie jedną metodę? <code cpp>#​include "​Kula.h"​ 
-#include "​Kolo.h"​ 
-#include <​iostream>​ 
- 
-using namespace std; 
- 
-int main(){ 
-  Kula k(0,​0,​0,​10);​ 
-} 
-</​code>​ 
-  - [2 plusy] Dopisz brakujące metody z przykładu [[#​funkcje_wirtualne|Funkcje wirtualne]] i uruchom program. 
-  - **[2 punkty] Napisz klasę abstrakcyjną //​Ksztalt//,​ która będzie posiadała jedna czystą metodę wirtualną //rysuj//. Następnie napisz kilka klasę dziedziczących po tej klasie (//​Trójkąt,​ Kwadrat, Koło//) i odpowiednio implementujących metodę //rysuj//. Metoda powinna rysować kształty w trybie tekstowym. \\ Zadeklaruj listę, która przechowuje obiekty klasy //Ksztalt// i uzupełnij ją losowo //Kolami, Kwadratami//​ albo //​Trojkatami//​. Następnie wywołaj na każdym obiekcie z listy metodę //rysuj//. Jaki jest tego efekt?** 
- 
-<WRAP center round info 60%> 
-Jeśli chcesz zobaczyć jak można wykorzystać dziedziczenie i polimorfizm w większych przykładach,​ zapraszam do zapoznania się z laboratorium [[.:​dziedziczenie-ex|Dziedziczenie i Polimorfizm -- Przykłady]] 
-</​WRAP>​ 
- 
-<WRAP center round important 60%> 
-Za tydzień kolokwium! Czy pamiętasz o nim? 
- 
-Informacje organizacyjne i przykładowe pytania znajdują się [[..:​start#​kolokwium|tutaj]]. 
-</​WRAP>​ 
  
pl/dydaktyka/jimp2/2016/labs/dziedziczenie.txt · ostatnio zmienione: 2019/06/27 15:50 (edycja zewnętrzna)
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0