STL, Iteratoren - public.fh
Transcription
STL, Iteratoren - public.fh
Die verschiedenen Programmierparadigmen von C++ © Helmke Die verschiedenen Programmierparadigmen von C++ 5 Die Standard Template Library (STL) © Helmke Klasse Komponente Code auf dieser Seite verstanden 1. Verstehe nur Bahnhof class Komponente { 2. Verstehe die Schnittstelle public: und den kompletten Code Komponente() {}; 3. Verstehe das Problem, void addLieferant(const Lieferant& ); void entferneLieferant(const Lieferant& ); aber nicht jedes Schlüsselwort bool hatLieferant(const Lieferant& li) const; private: 5.1 Container 5.2 Iteratoren (*) 5.3 Funktionsobjekte 5.4 Algorithmen (*) Auch in dieser Datei Komponente(const Komponente&); /* verbotener Aufruf */ Komponente& operator=(const Komponente&); /* verbotener Aufruf */ vector<Lieferant> container; }; void Komponente::addLieferant(const Lieferant& li) { /*… */ } void Komponente:: entferneLieferant(const Lieferant& li ) { /*… */ } bool Komponente:: hatLieferant(const Lieferant& li) const { /*… */ } Lehrbuch: 6.4 Kompendium, 3. Auflage: 8.12, 11.5 Kompendium, 4. Auflage: 11.5 WS 2015/16 V 4.01; © Hon. Prof. Helmke Die verschiedenen Programmierparadigmen von C++ 1 © Helmke WS 2015/16 V 4.01; © Hon. Prof. Helmke Die verschiedenen Programmierparadigmen von C++ Ergebnis: ___ 1 __ 2 __ 3 © Helmke Klasse Komponente (2) Klasse Komponente (3) void Komponente::addLieferant(const Lieferant& li) { container.push_back(li); } bool Komponente:: hatLieferant(const Lieferant& li) const { for (int i=0; i<container.size(); ++i) { if (container[i] == li) { Container werden normalerweise return true; aber mit Iteratoren durchlaufen. } } return false; } bool Komponente:: hatLieferant(const Lieferant& li) const { for (vector<Lieferant>::iterator iter = container.begin(); iter != container.end(); ++iter) { if (*iter == li) { return true; } } } return false; } WS 2015/16 V 4.01; © Hon. Prof. Helmke 10 WS 2015/16 V 4.01; © Hon. Prof. Helmke 9 11 Die verschiedenen Programmierparadigmen von C++ © Helmke Iteratoren (2) Die verschiedenen Programmierparadigmen von C++ © Helmke Die Iteratoren begin und end Die Schnittstelle der Iteratoren entspricht genau der Art und Weise wie Zeiger in C über Arrays wandern. Im Unterschied zu Zeigern können Iteratoren aber mit beliebigen Containern und nicht nur mit Arrays umgehen. Sie können aber auch mit normalen Arrays umgehen. begin() iter end() Iteratoren von verschiedenen Containern besitzen unterschiedliche Typen, aber die gleiche Schnittstelle. Um mit Containern arbeiten zu können, stellen Container entsprechende Elementfunktionen bereit, die beiden wichtigsten sind begin() und end(). WS 2015/16 erstes Element V 4.01; © Hon. Prof. Helmke 12 Die verschiedenen Programmierparadigmen von C++ © Helmke Klasse Komponente (3) Code auf dieser Seite verstanden bool Komponente:: hatLieferant(const Lieferant& li) const { 1. Verstehe nur Bahnhof for (int i=0; i<container.size(); ++i) { 2. Prinzip der Iteratoren als eine Art if (container[i] == li) { return true; Zeiger verstanden } } 3. Verstanden, könnte Code dieser return false; Methode nun selbst schreiben } 4. Kannte Iteratoren schon vorher bool Komponente:: hatLieferant(const Lieferant& li) const { for (vector<Lieferant>::iterator iter = container.begin(); iter != container.end(); ++iter) { if (*iter == li) { return true; } } } return false; } WS 2015/16 V 4.01; © Hon. Prof. Helmke Ergebnis: ___ 1 __ 2 __ 3 __4 14 WS 2015/16 letztes Element V 4.01; © Hon. Prof. Helmke Die verschiedenen Programmierparadigmen von C++ 13 © Helmke Iteratoren (3) #include <list> using namespace std; container::begin() liefert einen Iterator, der die Position des ersten Elements im Container repräsentiert. list<float> cont; for (int i=1; i<4; ++i) { cont. push_front(i*2.2); cont. push_back(i*1.1); } container::end() liefert einen Iterator, der die Position hinter dem letzten Element im Container repräsentiert. // Alle Elemente über Iteratoren // ausgeben: list<float>::iterator iter = cont.begin(); for (; iter != cont.end(); ++iter) { cout << *iter << ' '; } WS 2015/16 Falls begin() gleich end(), ist der Container leer (container::empty() liefert true). V 4.01; © Hon. Prof. Helmke 15 Die verschiedenen Programmierparadigmen von C++ © Helmke Iteratoren (4) Die verschiedenen Programmierparadigmen von C++ Iteratoren (4) Ausgabe ? 1. 0 1 2. 1 2 1 2 3. 1 2 4. 1 1 #include <list> using namespace std; list<int> cont; for (int i=1; i<3; ++i) { cont. push_back(i); } // Alle Elemente über Iteratoren // ausgeben: list<int>::iterator iter = cont.begin(); Ergebnis __ 0 1 _-_ 1 2 Der Typ des Iterators ist jeweils im zugehörigen Container per typedef festgelegt, z.B.: class list { public: typedef ... iterator; ... }; __ 1 2 1 2 __ 1 1 Der genaue Typ ist implementierungsabhängig. Daneben gibt es auch den Typ const_iterator. Bei ihm ist über den *-Operator (bzw. ->-Operator) nur ein konstanter (nicht modifizierender) Zugriff auf das Element, auf das der Iterator verweist, möglich. for (; iter != cont.end(); ++iter) { cout << *iter << ' '; } Hiervon ist jedoch der Typ „const WS 2015/16 © Helmke V 4.01; © Hon. Prof. Helmke Die verschiedenen Programmierparadigmen von C++ 16 © Helmke Iteratoren (6) WS 2015/16 iterator“ zu unterscheiden. V 4.01; © Hon. Prof. Helmke Die verschiedenen Programmierparadigmen von C++ 17 © Helmke Überladung des Increment-Operators class iterator /*...*/ { /*...*/ for (cont.begin(); iter != cont.end(); ++iter) { cout << *iter << ' '; } iterator& operator++() { „nächste Position markieren“ return *this; } Sofern die Elemente des Containers Objekte mit Komponenten sind, sind (normalerweise) auch der Operator „->“ und „.“ (Punkt) definiert: iter-> komp (*iter). komp iterator operator++(int) { iterator temp = *this; „nächste Position markieren“ return temp; } In einigen Implementierungen der STL kann der „->“-Operator aber evtl. nicht definiert sein. (*iter).komp funktioniert allerdings immer. list<int>:::iterator iter1, iter2; ... // iter1 zeige auf Liste mit // den Elementen 1,2,3,4, // und zwar auf 1 iter2 = ++iter1; // iter2 und iter1 zeigen nun auf 2 // iter1 zeige vorher auf 2 iter2 = iter1++; // iter1 zeigt nun auf 3, // iter2 zeigt auf 2 /*...*/ }; WS 2015/16 V 4.01; © Hon. Prof. Helmke 18 WS 2015/16 V 4.01; © Hon. Prof. Helmke 19 Die verschiedenen Programmierparadigmen von C++ © Helmke Die verschiedenen Programmierparadigmen von C++ Klasse Komponente (4): Besser mit const_iterator Klasse Komponente (5): Besser mit const_iterator bool Komponente:: hatLieferant(const Lieferant& li) const { for (vector<Lieferant>::const_iterator iter = container.begin(); iter != container.end(); ++iter) { if (*iter == li) { return true; } Dieser Suchvorgang kommt ganz häufig vor: } Von Anfang bis Ende prüfen, ob ein } Element des Containers gleich einem anderen ist. return false; } bool Komponente:: hatLieferant(const Lieferant& li) const { for (vector<Lieferant>::const_iterator iter = container.begin(); iter != container.end(); ++iter) { if (*iter == li) { return true; } } } return false; } bool Komponente:: hatLieferant(const Lieferant& li) const { vector<Lieferant>::const_iterator iter= find(container.begin(), container.end(), li); if (iter == container.end()) { return false;} else {return true;} } bool Komponente:: hatLieferant(const Lieferant& li) const { for (auto iter = container.begin(); iter != container.end(); ++iter) { … bool Komponente:: hatLieferant(const Lieferant& li) const { for (auto iter WS 2015/16 : container) { // iter wäre hier vom Typ Lieferant. V 4.01; © Hon. Prof. Helmke Die verschiedenen Programmierparadigmen von C++ 20 © Helmke WS 2015/16 21 © Helmke Wir bauen uns unser eigenes find. int array[100]; /* ..*/ find(&array[10], &array[49], 99) bool Komponente:: hatLieferant(const Lieferant& li) const { vector<Lieferant>::const_iterator iter= find(container.begin(), container.end(), li); if (iter == container.end()) { return false;} else {return true;} } template <typename T, typename W> T find (T anf, T ende, W value) { while (anf != ende && !(*anf == value) ) { // Reihenfolge wichtig ++anf; } return anf; bool Komponente:: hatLieferant(const Lieferant& li) const { if (find(container.begin(), container.end(), li) == container.end()) { return false;} else {return true; } } } bool Komponente:: hatLieferant(const Lieferant& li) const { return (find(container.begin(), container.end(), li) != container.end()); } V 4.01; © Hon. Prof. Helmke V 4.01; © Hon. Prof. Helmke Die verschiedenen Programmierparadigmen von C++ Klasse Komponente (5): Besser mit const_iterator WS 2015/16 © Helmke bool Komponente:: hatLieferant(const Lieferant& li) const { return (find(container.begin(), container.end(), li) != container.end()); } 22 WS 2015/16 V 4.01; © Hon. Prof. Helmke 23 Die verschiedenen Programmierparadigmen von C++ © Helmke Algorithmus find Die verschiedenen Programmierparadigmen von C++ © Helmke Algorithmus for_each #include <algorithm> using namespace std; template <class InputIter, class Tp> inline InputIter find(InputIter first, InputIter last, const Tp& val) { while (first != last && *first != val) { ++first; } return first; } void MachWas(int i) { cout << i*i; } int array[100]; for_each(&array[0], & array[100], MachWas); sort(&array[10], & array[20]); template <typename Iter, typename Func> void for_each(Iter anf, Iter end, Func f){ while (anf!= end) { f(*anf); ++anf; } } WS 2015/16 V 4.01; © Hon. Prof. Helmke Die verschiedenen Programmierparadigmen von C++ 24 © Helmke WS 2015/16 V 4.01; © Hon. Prof. Helmke Die verschiedenen Programmierparadigmen von C++ Beispiele für Algorithmen Clicker vector<int> vec; ... list<int> cont; for (int i=1; i<13; ++i) { cont. push_back( i ); } vector<int>::iterator pos; pos = min_element(vec.begin(), vec.end()); cout << * max_element(vec.begin(), vec.end()); sort(vec.begin(), vec.end()); ... Ausgabe? 1. 12 12 12 2. 12 12 11 3. 13 12 12 4. 12 11 12 Ergebnis: _-__ 12 12 12 ___12 12 11 __ 13 12 12 __ 12 11 12 // Ab dem zweiten Element nach 5 suchen pos = find(vec.begin()+1, vec.end(), 5); V 4.01; © Hon. Prof. Helmke © Helmke cout << * max_element(vec.begin(), vec.end()); cout << * max_element(vec.begin()++, vec.end()); cout << * max_element(++vec.begin(), vec.end()); // Reihenfolge vom zweiten bis vorletzten Element umkehren reverse(vec.begin()+1, vec.end()-1); ... WS 2015/16 25 26 WS 2015/16 V 4.01; © Hon. Prof. Helmke 27 Die verschiedenen Programmierparadigmen von C++ © Helmke Clicker Die verschiedenen Programmierparadigmen von C++ © Helmke Algorithmen -- Fortsetzung list<int> vec; for (int i=1; i<13; ++i) { vec. push_front( i ); } Der Anwender eines Algorithmus muss selbst darauf achten, dass das übergebene Ende vom Anfang aus erreichbar ist, d.h. die Iteratoren müssen zumindest zum gleichen Container gehören. Ausgabe? 1. 12 12 11 2. 1 1 2 3. 13 12 12 4. 12 11 12 Die Bereiche der Algorithmen werden immer als halboffenene Intervalle ausgewertet: [anfang, ende) bedeutet anfang...ende-1. Beispiel: // vec sei mit Zahlen von 1 bis 20 gefüllt vector<int>::iterator pos5 = find(vec.begin(), vec.end(), 5); vector<int>::iterator pos13 = find(vec.begin(), vec.end(), 13); cout << * max_element(vec.begin(), vec.end()); cout << * max_element(vec.begin()++, vec.end()); cout << * max_element(++vec.begin(), vec.end()); // 12 wird ausgegeben, da Intervall [pos5, pos13 [ bearbeitet wird cout << *max_element(pos5, pos13); // 13 wird ausgegeben. cout << *max_element(pos5, ++pos13); Ergebnis: _-__ 12 12 11 ___1 1 2 __ 13 12 12 __ 12 11 12 WS 2015/16 V 4.01; © Hon. Prof. Helmke 28 Die verschiedenen Programmierparadigmen von C++ © Helmke Clicker: Algorithmen auch für Arrays int arr[20]; for (int i=0; i<20; ++i) { arr[i] = i * i; } WS 2015/16 Die verschiedenen Programmierparadigmen von C++ array<int, 20> arr; // int arr[20]; for (int i=0; i<20; ++i) { arr[i] = i * i; } Ausgabe? 1. 19 8 10 2. 361 64 100 3. 361 64 Fehler 4. 361 100 11 © Helmke Ausgabe? 1. 19 8 10 2. 361 64 100 3. 361 64 Fehler 4. 361 100 11 cout << * max_element(&arr[4], & arr[20]); cout << * find(&arr[4], & arr[10], 64); cout << * find(&arr[4], & arr[10], 144); Ergebnis: __ 19 8 10 _-_ 361 64 100 __ 361 64 Fehler _361 100 11 V 4.01; © Hon. Prof. Helmke 29 Clicker: Algorithmen auch für STL-Arrays cout << * max_element(&arr[4], & arr[20]); cout << * find(&arr[4], & arr[10], 64); cout << * find(&arr[4], & arr[10], 144); WS 2015/16 V 4.01; © Hon. Prof. Helmke Ergebnis: __ 19 8 10 _-_ 361 64 100 __ 361 64 Fehler _361 100 11 30 WS 2015/16 V 4.01; © Hon. Prof. Helmke 31 Die verschiedenen Programmierparadigmen von C++ © Helmke Algorithmen – Beispiel: Definition von max_element Trauen Sie sich auch zu find selbst als Template zu implementieren 1. Versteh nur Bahnhof 2. Ja, mit Blick auf die Folie 3. Sollte ohne Hilfe (evtl. nicht so schön) klappen ForwardIter result = first; while (++first != last) { if (*result < *first) { result = first; Trauen Sie sich zu,min_element selbst als Template } zu implementieren } 1. Versteh nur Bahnhof return result; 2. Ja, mit Blick auf die Folie } 3. Sollte ohne Hilfe (evtl. nicht so schön) klappen V 4.01; © Hon. Prof. Helmke Die verschiedenen Programmierparadigmen von C++ Ergebnis: ___ 1 __ 2 32 © Helmke __ 3 WS 2015/16 V 4.01; © Hon. Prof. Helmke Die verschiedenen Programmierparadigmen von C++ Dann mal los! Algorithmus remove int arr[20]; for (int i=0; i<20; ++i) { arr[i] = i * i; } #include <iostream> #include <list> #include<algorithm> using namespace std; int main() { list<int> cont; for (int i=1; i<6; ++i) { cont.push_back(i); } cout << * MeinMinElement(&arr[4], & arr[20]); arr[14] = -4; cout <<" " << * MeinMinElement(&arr[0], & arr[20]); template <class ForwardIter> ForwardIter max_element(ForwardIter first, ForwardIter last) { if (first == last) return first; ForwardIter result = first; while (++first != last) { if (*result < *first) { result = first; } } return result; 33 © Helmke cout << "Liste vor remove :"; // Ausgabe ist nun 1 2 3 4 5 copy(cont.begin(), cont.end(), ostream_iterator<int>(cout, " ")); // 3 mit remove entfernen list<int>::iterator new_end = remove(cont.begin(), cont.end(), 3); Gewünschte Ausgabe 16 -4 // Ausgabe ist nun 1 2 4 5 5 copy(cont.begin(), cont.end(), ostream_iterator<int>(cout, " ")); } } WS 2015/16 © Helmke Clicker-Auswertung template <class ForwardIter> ForwardIter max_element(ForwardIter first, ForwardIter last) { if (first == last) return first; WS 2015/16 Die verschiedenen Programmierparadigmen von C++ V 4.01; © Hon. Prof. Helmke 34 WS 2015/16 V 4.01; © Hon. Prof. Helmke 35 Die verschiedenen Programmierparadigmen von C++ © Helmke Algorithmus remove (2) Die verschiedenen Programmierparadigmen von C++ © Helmke Elementfunktion erase Warum wird ein Element nicht wirklich gelöscht? copy(cont.begin(), cont.end(), ostream_iterator<int>(cout, " ")); // Ausgabe sei vorher 1 2 3 4 5 Algorithmen können die Container nicht wirklich verkleinern oder vergrößern, weil ihnen der Container selbst nicht übergeben wird, sondern nur Iteratoren, die irgendwo in den Container zeigen, z.B. // 3 mit remove entfernen cont.erase(remove(cont.begin(), cont.end(), 3), cont.end()); list<int>::iterator new_end = cont. end(); --new_end; --new_end; remove(cont.begin(), new_end, 3); copy(cont.begin(), cont.end(), ostream_iterator<int>(cout, " ")); // Ausgabe ist nun 1 2 4 5 Damit kann remove auf keinen Fall das end von cont verändern. Woher soll es das auch kennen. Dieses können nur Elementfunktionen, z.B. erase(cont::iterator a, cont::iterator e); Alle Elemente im Intervall [a..e) werden wirklich gelöscht. WS 2015/16 V 4.01; © Hon. Prof. Helmke Die verschiedenen Programmierparadigmen von C++ 36 © Helmke WS 2015/16 V 4.01; © Hon. Prof. Helmke Die verschiedenen Programmierparadigmen von C++ Löschen mit find remove ist aber mächtiger list<int> cont; for (int i=1; i<6; ++i) { cont.push_back(i); } cont. push_back(3); cont. push_back(3); list<int> cont; // Container-List fuer int for (int i=1; i<6; ++i) { cont.push_back(i); } cont. push_front(3); cont. push_front(3); copy(cont.begin(), cont.end(), ostream_iterator<int>(cout, " ")); // Ausgabe ist nun 3 3 1 2 3 4 5 copy(cont.begin(), cont.end(), ostream_iterator<int>(cout, " ")); // Ausgabe ist nun 1 2 3 4 5 3 3 cont.erase(new_end, cont.end()); copy(cont.begin(), cont.end(), ostream_iterator<int>(cout, " ")); // Ausgabe ist nun Liste nach erase :1 2 4 5 cont.erase(iter3, iter3p1); copy(cont.begin(), cont.end(), ostream_iterator<int>(cout, " ")); // Ausgabe ist nun Liste nach erase :1 2 4 5 3 3 V 4.01; © Hon. Prof. Helmke © Helmke list<int>::iterator new_end = remove(cont.begin(), cont.end(), 3); copy(cont.begin(), cont.end(), ostream_iterator<int>(cout, " ")); // Ausgabe ist nun 1 2 4 5 3 4 5 list<int>::iterator iter3= find(cont.begin(), cont.end(), 3); list<int>::iterator iter3p1=iter3; ++iter3p1; WS 2015/16 37 38 WS 2015/16 V 4.01; © Hon. Prof. Helmke 39 Die verschiedenen Programmierparadigmen von C++ © Helmke Die verschiedenen Programmierparadigmen von C++ Clicker Clicker list<int> cont; 2. Ausgabe? for (int i=1; i< 6; ++i) { 1. 1 2 3 4 5 cont. push_back( i ); 2. 1 2 4 5 5 } 3. 1 2 4 5 // Ausgabe ist 1 2 3 4 5 4. 1 2 4 5 3 copy(cont.begin(), cont.end(), ostream_iterator<int>(cout, " ")); remove(cont.begin(), cont.end(), 3); // 2. Ausgabe??? copy(cont.begin(), cont.end(), ostream_iterator<int>(cout, " ")); list<int> cont; for (int i=1; i< 6; ++i) { cont. push_back( i ); } // Ausgabe ist 1 2 3 4 5 Ergebnis: __ 1 2 3 4 5 _--_ keine Ausgabe __ 1 2 4 5 __ 1 2 4 5 3 V 4.01; © Hon. Prof. Helmke 40 Die verschiedenen Programmierparadigmen von C++ © Helmke Clicker WS 2015/16 V 4.01; © Hon. Prof. Helmke Die verschiedenen Programmierparadigmen von C++ 41 © Helmke Komponente::entferneLieferant list<int> cont; for (int i=1; i< 6; ++i) { cont. push_back( i ); } void Komponente:: entferneLieferant(const Lieferant& li ) { vector<Lieferant>::iterator iter = find(container.begin(). container.end(), li); if (iter != container.end()) { vector<Lieferant>::iterator hlp = iter; ++iter; container.erase(iter, hlp); }; } 2. Ausgabe? 1. 1 2 3 4 5 2. 1 2 4 5 5 3. 1 2 4 5 4. 1 2 4 5 3 // Ausgabe ist 1 2 3 4 5 copy(cont.begin(), cont.end(), ostream_iterator<int>(cout, " ")); cont.erase(remove(cont.begin(), cont.end(), 3), cont.end() ); copy(cont.begin(), cont.end(), ostream_iterator<int>(cout, " ")); void Komponente:: entferneLieferant(const Lieferant& li ) { container.erase( remove(container.begin(), container.end(), li), container.end()); } Ergebnis: __ 1 2 3 4 5 __ 1 2 4 5 5 _--_ 1 2 4 5 __ 1 2 4 5 3 WS 2015/16 2. Ausgabe? 1. 1 2 3 4 5 2. 3. 1 2 4 5 4. 1 2 4 5 3 copy(cont.begin(), cont.end(), ostream_iterator<int>(cout, " ")); remove(cont.begin(), cont.end(), 3 ); cont.erase(cont.begin(), cont.end()); copy(cont.begin(), cont.end(), ostream_iterator<int>(cout, " ")); Ergebnis: __ 1 2 3 4 5 _--_ 1 2 4 5 5 __ 1 2 4 5 __ 1 2 4 5 3 WS 2015/16 © Helmke V 4.01; © Hon. Prof. Helmke 42 WS 2015/16 V 4.01; © Hon. Prof. Helmke 43