Różnice

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

Odnośnik do tego porównania

Nowa wersja
Poprzednia wersja
pl:dydaktyka:jimp2:2017:labs:dziedziczenie [2017/02/25 12:56]
127.0.0.1 edycja zewnętrzna
pl:dydaktyka:jimp2:2017:labs:dziedziczenie [2019/06/27 15:50] (aktualna)
Linia 61: Linia 61:
 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. 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ą!+Jeśli klasa bazowa ma przeciążony **operator=**,  nie zostanie on odziedziczony ​przez klasę pochodną! ​(pozostałe zostaną odziedziczone)
  
 <code cpp> <code cpp>
Linia 125: Linia 125:
 {{.:​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.
Linia 132: Linia 132:
 #include <​iostream>​ #include <​iostream>​
 #include <​list>​ #include <​list>​
 +#include <​memory>​
  
 using namespace std; using namespace std;
  
 class Umowa{ class Umowa{
-  protected: 
-    double wynagrodzenieBrutto;​ 
   public:   public:
-    Umowa(double pensja):wynagrodzenieBrutto(pensja){};​ +    Umowa(double pensja):wynagrodzenie_brutto_(pensja){};​ 
-    virtual double ​pobierzNetto(); +    virtual double ​PobierzNetto() const
-    double ​pobierzBrutto();+    double ​PobierzBrutto() const; 
 +  protected:​ 
 +    double wynagrodzenie_brutto_;
 }; };
  
 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{
-  private: 
-    string imie,​nazwisko,​pesel;​ 
-    Umowa* umowa; 
   public:   public:
-    Pracownik(string ​i,​string ​n,​string ​p,Umowa* u+    Pracownik(string ​imie,​string ​nazwisko,​string ​pesel,std::​unique_ptr<​Umowa> umowa
-      :imie(i),nazwisko(n),pesel(p),umowa(u){}; +      :imie_{imie},nazwisko_{nazwisko},pesel_{pesel},umowa_{move(umowa)}{}; 
-    Pracownik(const Pracownik&​);​ +    double ​PobierzPensje() const
-    ~Pracownik(){}; +    friend ​std::ostream&​ operator<<​(std::ostream&,​Pracownik&​)
-    double ​pobierzPensje(); +  private: 
-    friend ostream&​ operator<<​(ostream&,​Pracownik&​);​+    std::string imie_,​nazwisko_,​pesel_;​ 
 +    std::​unique_ptr<​Umowa>​ umowa_;
 };</​code>​ };</​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==== ====Destruktory wirtualne====
Linia 214: Linia 364:
     // 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>​
Linia 301: Linia 451:
 <WRAP center round important 60%> <WRAP center round important 60%>
 **UWAGA**\\ **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-**wraz z uzasadnieniami**. Zadanie bez właściwego uzasadnienia nie będzie zaliczone.+Przesyłając rozwiązania zadań należy zamieścić (w formie komentarza w kodzie) odpowiedzi na problemy postawione w zadaniach ​5-**wraz z uzasadnieniami**. Zadanie bez właściwego uzasadnienia nie będzie zaliczone.
 </​WRAP>​ </​WRAP>​
  
 +  - [2 plusy] Zdefiniować metody wypełniania tablicy std::​vector<​int>:​
 +    * 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) <code cpp>​IncrementalFill(int start, int step = 1)</​code>​
 +    * 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 <​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:
 +    * 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 <​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:
Linia 327: Linia 494:
 </​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>​+
  
-<WRAP center round important 60%+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: 
-Za tydzień kolokwiumCzy pamiętasz nim?+<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 ​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"​};​
  
-Informacje organizacyjne i przykładowe pytania znajdują się [[..:start#​kolokwium|tutaj]]+  for (const auto &p : Zipper(vi, vs)) { 
-</WRAP>+    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>​
pl/dydaktyka/jimp2/2017/labs/dziedziczenie.1488023818.txt.gz · ostatnio zmienione: 2019/06/27 15:52 (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