Różnice

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

Odnośnik do tego porównania

pl:dydaktyka:jimp2:2017:labs:dziedziczenie [2017/04/11 10:05]
kkutt [Ćwiczenia]
pl:dydaktyka:jimp2:2017: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>​ 
- 
-==== Stary dobry C ==== 
- 
-Przypomnijmy sobie zadanie z wcześniejszych laboratoriów z utworzeniem tablicy jednowymiarowej. Wtedy algorytm składał się z dwóch kroków alokacja pamięci i inicjalizacja wartości tablicy. Ale co powinniśmy zrobić, gdyby była potrzeba zdefiniowania kilku różnych sposobów wypełniania tablicy w zależności od tego co zażyczy sobie użytkownik (a więc nie możemy tego przewidzieć w trakcie pisania programu) :?: 
- 
-Kod mógłby wyglądać następująco:​ 
-<code cpp> 
-int *CreateArray(size_t size, int version_of_filling) { 
-   int *tab = NewArray(size);​ 
-   ​FillArray(tab,​ size, version_of_filling);​ 
-} 
- 
-void FillArray(int *tab, size_t size, int version_of_filling) { 
-   ​switch(version_of_filling) { 
-     case 0: {//INIT TO 0s 
-        for (int i=0; i<size; i++) { 
-           ​tab[i] = 0; 
-        } 
-        break; 
-     } 
-     case 1: {//INIT TO NUMBER? 
-        for (int i=0; i<size; i++) { 
-           ​tab[i] = NUMBER; //skąd ją wziąć? dołożyć nowy parametr do obydwu funkcji? 
-        } 
-        break; 
-     } 
-     //​tutaj trzeba będzie pamiętać o dołożeniu kolejnego algorytmu jak pojawi się kolejne rozszerzenie 
-   } 
-} 
-</​code>​ 
- 
-Ten kod ma jednak sporo wad przy większej ilości i komplikacji algorytmów metoda Fill się niebezpiecznie rozrasta. Należy pamiętać, że jak pojawi się nowe wymaganie to należy dodać nowy warunek do switcha, itd... 
- 
-Czy da się to zrobić jednak inaczej? Tak z wykorzystaniem wskaźników do funkcji :!: 
- 
-<code cpp> 
-int *CreateArray(size_t size, int (*filler)(int)) { 
-   int *tab = NewArray(size);​ 
-   ​FillArray(tab,​ size, version_of_filling);​ 
-} 
- 
-void FillArray(int *tab, size_t size, int (*filler)(int)) { 
-    for (int i=0; i<size; i++) { 
-       ​tab[i] = filler(i); 
-    } 
-} 
-</​code>​ 
- 
-Kod FillArray znacznie się uprościł, ale żeby pozostawić poprzednią funkcjonalność musimy jeszcze dopisać brakujące metody. Każda metoda, która spełnia zadany interfejs się nada (tzn. metoda musi zwracać int jako wartość pola tablicy i przyjmować int jako index ustawianego pola): 
-<code cpp> 
-int UniformFillWithZero(int index) { 
-  return 0; 
-} 
- 
-int UniformFillWith77(int index) { 
-  return 77; 
-} 
- 
-int IncrementalFill(int index) { 
-  return 8 * index + 14; 
-} 
-</​code>​ 
- 
-I przykładowe wywołanie kodu: 
-<code cpp> 
-int * tab = CreateArray(1024,​ IncrementalFill);​ 
-</​code>​ 
- 
-Znacznie lepiej, ale wciąż musimy zadeklarować mnóstwo funkcji jeśli byśmy chcieli chociażby wypełnić tablicę identycznymi liczbami, ale każdą kolejną tablicę inną wartością. Nasze funkcje są pozbawione niestety kontekstu :!: 
- 
-Jednak kontekst można dołożyć do każdej z tych funkcji w postaci pomocniczej struktury danych, która dodatkowo będzie przechowywać kontekst dla funkcji: 
- 
-<code cpp> 
-struct Filler { 
-  int (*fill)(Filler *, int); 
-}; 
- 
-void FillArray(int *tab, size_t size, Filler *filler) { 
-    for (int i=0; i<size; i++) { 
-       ​tab[i] = filler->​fill(filler,​ i); 
-    } 
-} 
-</​code>​ 
- 
-I wypełniacze:​ 
-<code cpp> 
-//​sepcyficzna struktura kontekstu pasująca do jednorodnego wypełniacza 
-struct UniformFiller { 
-  int (*fill)(UniformFiller *,int); 
-  int value; 
-}; 
- 
-//context to self z Pythona! 
-int UniformFillerMethod(UniformFiller *context, int index) { 
-  return context->​value;​ 
-} 
- 
-//​inicjalizacja naszej struktury danych ustawienie wskaźnika do odpowiedniej funkcji 
-//Python i C++ robią to automatycznie 
-UniformFiller f; 
-f.fill = UniformFillerMethod; ​ 
-f.value = 77; 
- 
-//​wywołanie utworzenia tablicy 
-int *tab = CreateTable(1024,​(Filler*)f);​ 
-</​code>​ 
- 
-==== Wracając do C++ ==== 
-Zdefiniowanie w klasie metody jako wirtualnej jest równoważne do zdefiniowania wskaźnika do funkcji. Klasa pochodna ustawia tylko wskaźnik na swoją wersję metody. Jeśli definiujemy metodę wirtualną jako abstrakcyjną jest to równoważne z nieustawieniem żadnej metody dla wskaźnika funkcji. Context jest automatycznie przesyłany do metody jako wskaźnik this.  
- 
-A teraz kod (odpowiednik struktury Filler z C i metodą Value odpowiednikiem wskaźnika do funkcji fill): 
- 
-<code cpp> 
-class ArrayFill { 
- ​public:​ 
-  virtual int Value(int index) const =0; 
-}; 
-</​code>​ 
- 
-Odpowiednik FillArray: 
-<code cpp> 
-void FillArray(size_t size, const ArrayFill &​filler,​ std::​vector<​int>​ *v) { 
-  v->​clear();​ 
-  v->​reserve(size);​ 
-  for (size_t i = 0; i < size; i++) { 
-    v->​emplace_back(filler.Value(i));​ 
-  } 
-} 
-</​code>​ 
- 
-I wreszcie odpowiednik UniformFiller (C%%++%% sam ustawi nasz wskaźnik na odpowiednią metodę, do tego jak się pomylimy kompilator jest w stanie wychwycić czy w ogóle przesłaniamy odpowiednią metodę i czy ona instnieje: 
-<code cpp> 
-class UniformFill : public ArrayFill { 
- ​public:​ 
-  UniformFill(int value = 0) : value_{value} {} 
-  virtual int Value(int index) const override; 
- ​private:​ 
-  int value_; 
-}; 
- 
- 
-int UniformFill::​Value(int index) const { 
-  return value_; 
-} 
-</​code>​ 
- 
-I wreszcie wywołanie: 
-<code cpp> 
-std::​vector<​int>​ vs; 
-FillArray(1024,​ UniformFill {77}, &vs); 
-</​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ń należy zamieścić (w formie komentarza w kodzie) odpowiedzi na problemy postawione w zadaniach 5-7 **wraz z uzasadnieniami**. Zadanie bez właściwego uzasadnienia nie będzie zaliczone. 
-</​WRAP>​ 
- 
-  - [2 plusy] Zdefiniować metody wypełniania tablicy std::​vector<​int>:​ 
-    * jednorone (zawsze ta sama wartość), z wartością domyślną 0 
-    * z inkrementacją (uwzględniająca wartość początkową start i krok step, który ma wartość domyślną 1) 
-    * za pomocą generatora liczb losowych 
-    * z kwadratem indeksu (a*index^2+b),​ zarówno a i b mogą przyjąć domyślne wartości 
-  - [3 plusy] Przygotować klasę abstrakcyjną StudentComparator z abstrakcyjną metodą <code cpp>bool IsLess(const Studnet &left, const Student &​right)</​code>​ i zdefiniowanym operatorem wywołania funkcji, delegującym zachowanie do abstrakcyjnej meteody. Następnie zdefiniować różne implementacje porównywania studentów: 
-    * ByFirstNameAscending 
-    * ByFirstNameDescending 
-    * ByLastNameAscending 
-    * ByProgramAscendingEmptyFirst,​ porównuje kierunki studiów alfabetycznie ale przesuwa na początek nieustawiony program (string pusty) 
-    * ByProgramAscendingEmptyLast,​ j.w. ale przesuwa na koniec nie ustawiony program studiów 
-    * [1 plus] w programie main wykorzystać wybrany porównywacz do posortowania wektora studentów (algorytm sort z biblioteki algorithm) 
-  - [2 plusy] Zdefiniować klasę abstrakcyjną Query z pojedynczą abstrakcyjną metodą <code cpp>bool Accept(const Student &​student);</​code>​. Klasa query reprezentuje ogólne zapytanie do repozytorium stuentów. Klasa repozytorium powinna udostępniać nową metodę <code cpp>​std::​vector<​Student>​ FindByQuery(const Query &​query)</​code>​ która przegląda wszystkich zgromadzonych studentów i każdego po kolei przekazuje do zaakceptowania,​ jeśli student został zaakceptowany powinien znaleźć się w wynikowym wektorze. Zdefiniować następnie implementacje zapytań: 
-    * [2 plusy] 
-      * ByFirstName 
-      * ByLastName 
-      * ByOneOfPrograms 
-      * ByYearLowerOrEqualTo 
-    * [2 plusy] 
-      * OrQuery ​ 
-      * AndQuery 
-  - [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 wskaźniki na 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>​ 
  
pl/dydaktyka/jimp2/2017/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