Both sides previous revision
Poprzednia wersja
Nowa wersja
|
Poprzednia wersja
|
pl:dydaktyka:jimp2:2017:labs:dziedziczenie [2017/04/11 11:33] kkutt [Ćwiczenia] |
pl:dydaktyka:jimp2:2017:labs:dziedziczenie [2019/06/27 15:50] (aktualna) |
{{.:umowa.png|Umowa - diagram UML}} | {{.: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**. | 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 albo //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. | **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. |
#include <iostream> | #include <iostream> |
#include <list> | #include <list> |
| #include <memory> |
| |
using namespace std; | using namespace std; |
| |
class Umowa{ | class Umowa{ |
| public: |
| Umowa(double pensja):wynagrodzenie_brutto_(pensja){}; |
| virtual double PobierzNetto() const; |
| double PobierzBrutto() const; |
protected: | protected: |
double wynagrodzenieBrutto; | double wynagrodzenie_brutto_; |
public: | |
Umowa(double pensja):wynagrodzenieBrutto(pensja){}; | |
virtual double pobierzNetto(); | |
double pobierzBrutto(); | |
}; | }; |
| |
class UmowaDzielo: public Umowa{ | class UmowaDzielo: public Umowa{ |
public: | public: |
UmowaDzielo(double pensja):Umowa(pensja){}; | UmowaDzielo(double pensja):Umowa{pensja} {}; |
virtual double pobierzNetto(); | virtual double PobierzNetto() const override; |
}; | }; |
| |
class UmowaPraca: public Umowa{ | class UmowaPraca: public Umowa { |
public: | public: |
UmowaPraca(double pensja):Umowa(pensja){}; | UmowaPraca(double pensja):Umowa{pensja} {}; |
virtual double pobierzNetto(); | virtual double PobierzNetto() const override; |
}; | }; |
| |
class Pracownik{ | class Pracownik{ |
| public: |
| Pracownik(string imie,string nazwisko,string pesel,std::unique_ptr<Umowa> umowa) |
| :imie_{imie},nazwisko_{nazwisko},pesel_{pesel},umowa_{move(umowa)}{}; |
| double PobierzPensje() const; |
| friend std::ostream& operator<<(std::ostream&,Pracownik&); |
private: | private: |
string imie,nazwisko,pesel; | std::string imie_,nazwisko_,pesel_; |
Umowa* umowa; | std::unique_ptr<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> | };</code> |
| |
// MUSI jednak być zaimplementowana w pochodnej (lub ponownie | // MUSI jednak być zaimplementowana w pochodnej (lub ponownie |
// zadeklarowana jako czysta | // zadeklarowana jako czysta |
virtual double pobierzNetto() = 0; | virtual double PobierzNetto() = 0; |
}; | }; |
</code> | </code> |
| |
- [2 plusy] Zdefiniować metody wypełniania tablicy std::vector<int>: | - [2 plusy] Zdefiniować metody wypełniania tablicy std::vector<int>: |
* jednorone (zawsze ta sama wartość), z wartością domyślną 0 | * jednorone (zawsze ta sama wartość), z wartością domyślną 0 konstruktor klasy: <code cpp>UniformFill(int value = 0)</code> |
* z inkrementacją (uwzględniająca wartość początkową start i krok step, który ma wartość domyślną 1) | * z inkrementacją (uwzględniająca wartość początkową start i krok step, który ma wartość domyślną 1) <code cpp>IncrementalFill(int start, int step = 1)</code> |
* za pomocą generatora liczb losowych | * za pomocą generatora liczb losowych <code cpp>RandomFill(std::unique_ptr<std::default_random_engine> generator, std::unique_ptr<std::uniform_int_distribution<int>> distribution)</code> |
* z kwadratem indeksu (a*index^2+b), zarówno a i b mogą przyjąć domyślne wartości | * z kwadratem indeksu (a*index^2+b), zarówno a i b mogą przyjąć domyślne wartości <code>SquaredFill(int a = 1, int b = 0)</code> |
- [3 plusy] Przygotować klasę abstrakcyjną StudentComparator z abstrakcyjną metodą <code cpp>bool IsLess(const Student &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: | - [3 plusy] Przygotować klasę abstrakcyjną StudentComparator z abstrakcyjną metodą <code cpp>bool IsLess(const Student &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 | * ByFirstNameAscending |
* [2 plusy] | * [2 plusy] |
* OrQuery | * OrQuery |
* AndQuery | * AndQuery <code>AndQuery(std::unique_ptr<Query> left, std::unique_ptr<Query> right)</code> |
- [1 plus] Przetestuj przykład z sekcji [[#metody_skladowe|Metody składowe]] | - [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. | - [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: | * W klasie Kolo powinny znaleźć się następujące pola i metody: |
</code> | </code> |
- [2 plusy] Dopisz brakujące metody z przykładu [[#funkcje_wirtualne|Funkcje wirtualne]] i uruchom program. | - [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%> | ====== Zadanie domowe: ====== |
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> | |
| |
| Iteratory to uogólnienie wskaźników wykorzystywanych do iteratowania po tablicy. W c żeby przeiterować po tablicy i tylko odczytać jej elementy można było wykonać następujący kod: |
| <code c> |
| int tab[128]; |
| int *p; |
| int *tab_end = &tab[128]; |
| int value; |
| InitTab(tab); |
| for (p = &tab[0]; p != tab_end; ++p) { |
| value = *p; |
| } |
| </code> |
| Wraz z c++ przyszły klasy i przeciążanie operatorów i w tym języku da się uogólnić ten schemat na dowolny obiekt, który posiada odpowiednioo zdefiniowane metody: |
| <code cpp> |
| array<int,128> tab; |
| array<int,128>::iterator p; |
| array<int,128>::iterator tab_end = tab.end(); |
| int value; |
| InitTab(tab); |
| for (p = tab.begin(); p != tab_end; ++p) { |
| value = *p; |
| } |
| </code> |
| Ze względu na to, że kompilator udziela niezbyt jasnych informacji o błędach i o tym co jest niezbędne do zaimplementowania, spróbujemy wykorzystać dziedziczenie. W tym celu zdefiniujemy klasę bazową która mogłaby być wykorzystywana przez dowolną implementację iteratora: |
| - **[1 punkt]** (lab7_iterable_tests) przygotować klasę bazową //IterableIterator// udostępniającą następujący zestaw metod bez zdefiniowanego zachowania (abstrakcyjnych, czysto wirtualnych), wszystkie te metody mają być przesłanialne przez klasy pochodne, zastanowić się które z tych metod nie powinny modyfikować **this**: |
| * <code cpp>std::pair<int, std::string> Dereference()</code> - odpowiada operacji //cos = *it//, czyli jej implementacja w klasach pochodnych powinna zwracać wartość pokazywaną przez iterator w akutalnym stanie. Nie umożliwiamy edycji tego stanu, stąd wartość zwracana jest przez kopię, a nie referencję. |
| * <code cpp>IterableIterator &Next()</code> - odpowiada operacji //++it// jej implementacja w klasie pochodnej powinna przesuwać wskaźnik na następną wartość w sekwencji, jeśli nie ma następnej wartości powinna przesuwać wskaźnik tuż za koniec |
| * <code cpp>bool NotEquals(const IterableIterator &other)</code> - odpowiada operacji //it != other//, jej implementacja w klasie pochodnej powinna zwracać prawdę jeśli iterator other wskazuje na ten sam element sekwencji (względnem jego indeksu w sekwencji, a nie tylko jego wartości zwracanej przez //Dereference()//). |
| * Destruktor - jako jedyna metoda w tej klasie powinien mieć domyślną implementację, najlepiej skorzystać z pomocy kompilatora i kazać mu ją wygenerować (= defualt). |
| - **rozgrzewka** Należy zaimplementować klasę pochodną dla **IterableIterator**, a mianowicie: **ZipperIterator**, zipper iterator powinien być w stanie przeiterować po dwóch wektorach na raz, biorąc po kolei pierwszy element z pierwszego wektora i pierwszy element z drugiego wektora, drugi element z pierwszego i drugi element z drugiego, itd.. Najłatwiej to zrobić jeśli ZipperIterator będzie posiadał po dwa const iteratory do początu i końca obu wektorów. Wtedy Derefencja, Next i NotEquals są bardzo proste w implementacji. (Testy wymagają konstuktora postaci: <code cpp>explicit ZipperIterator(std::vector<int>::const_iterator left_begin, |
| std::vector<std::string>::const_iterator right_begin, |
| std::vector<int>::const_iterator left_end, |
| std::vector<std::string>::const_iterator right_end);</code> |
| - **[1 punkt]** przygotować |
| - klasę IterableIteratorWrapper opakowującą dowloną podklasę IterableIterator i udostępniającą operatory i fukncje potrzebne by klasa mogła być używana jako iterator c++ między innymi w rage for, czyli (zastanowić się, które z tych funkcji muszą być typu const): |
| * konstruktor: <code cpp>IterableIteratorWrapper(std::unique_ptr<IterableIterator> iterator)</code> |
| * <code cpp>bool operator!=(const IterableIteratorWrapper &other)</code> - powinna wywoływać NotEquals z przekazanych iteratorach (pole składowe i argument fukncji) |
| * <code cpp>std::pair<int, std::string> operator*()</code> - powinna wywoływać Dereference na polu składowym |
| * <code cpp>IterableIteratorWrapper &operator++()</code> - powinna wywoływać Next na polu składowym |
| - przygotować klasę bazową Iterable o następujących metodach (zastanowić się, które z tych funkcji muszą być typu const): |
| * <code cpp>std::unique_ptr<IterableIterator> ConstBegin()</code> - czysto abstrakcyjna metoda (bez domyślnej implementacji) przesłanialna w klasach pochodnych ma za zadanie zwracanie odpowiedniego iteratora do początku sekwencji dla właściwej klasy. |
| * <code cpp>std::unique_ptr<IterableIterator> ConstEnd()</code> - czysto abstrakcyjna metoda (bez domyślnej implementacji) przesłanialna w klasach pochodnych ma za zadanie zwracanie odpowiedniego iteratora za końcem sekwencji dla właściwej klasy. |
| * <code cpp>IterableIteratorWrapper cbegin() const</code> - konkretna metoda (nie wirtualna), która wywołuje ConstBegin i tworzy odpowiedni typ do zwrócenia. |
| * <code cpp>IterableIteratorWrapper cend() const</code> - konkretna metoda (nie wirtualna), która wywołuje ConstEnd i tworzy odpowiedni typ do zwrócenia. |
| * <code cpp>IterableIteratorWrapper begin() const</code> - konkretna metoda (nie wirtualna), która wywołuje cbegin() |
| * <code cpp>IterableIteratorWrapper end() const</code> - konkretna metoda (nie wirtualna), która wywołuje cend() |
| - **[3 punkty]** (lab7_iterable_zipper_tests, lab7_iterable_product_tests, lab7_iterable_enumerate_tests)przygotować klasy Zipper, Product, Enumerate, które implementują klasę Iterable i klasy ProductIterator i EnumerateIterator, które implementują klasę IterableIterator. |
| * klasa zipper ma za zadanie utworzenie możliwości przeglądania dwóch wektorów jednocześnie w pętli typu range for. |
| * klasa enumerate ma za zadanie utworzenie możliwości przeglądania jednego wektora w pętli typu range for, ale tak, że każda wartość wskazywana przez jej iterator zwraca parę indeks tego elementu, wartość tego elementu. |
| * klasa product ma za zadanie utworzenie możliwości przeglądania dwóch wektorów jednocześnie w pętli typu range for, ale w przeciwieństwie do zipper, ma tworzyć iloczy kartezjański wszystkich par <code cpp> |
| const vector<int> vi {4, 77, -91}; |
| const vector<string> vs {"4", "9991", "adfskld"}; |
| |
| for (const auto &p : Zipper(vi, vs)) { |
| cout << "(" << p.first << ", \"" << p.second << "\") "; |
| } |
| //wypisze: (4, "4") (77, "9991") (-91,"adfskld") |
| |
| for (const auto &p : Enumerate(vs)) { |
| cout << "(" << p.first << ", \"" << p.second << "\") "; |
| } |
| //wypisze: (0, "4") (1, "9991") (2,"adfskld") |
| |
| for (const auto &p : Product(vi,vs)) { |
| cout << "(" << p.first << ", \"" << p.second << "\") "; |
| } |
| //wypisze: (4, "4") (4,"9991") (4, "adfskld") (77, "4") (77,"9991") (77, "adfskld") (-91,"4") (-91,"9991") (-91, "adfskld") |
| |
| </code> |