Programovanie 3. 2012-2013 10. Prednáška - STL Ing. František Gyárfáš, PhD. gyarfas@ii.fmph.uniba.sk http://www.ii.fmph.uniba.sk/~gyarfas/
template<typename T> class POLE {... public: bool vloz(const T &vstup);... Cvičenie 8. template<typename T> class MNOZINA: public POLE<T> {... public: bool vloz(const T &vstup);... Umožňuje viacnásobné zadanie tej istej hodnoty. Neumožňuje viacnásobné zadanie tej istej hodnoty. Podobná situácia, ako pri vtákoch a pinguinoch.
Kopa dát
Kopa dát rovnakého typu 666666.777777 3.14 1222.1 4.32 8.12 7.8126054 9.32 7.7777 0.0 6.21 4.32 88.21-91919.9
Ako ich uložiť? Čo s nimi budem robiť? 666666.777777 3.14 1222.1 4.32 8.12 7.8126054 9.32 7.7777 0.0 6.21 4.32 88.21 Priamy prístup [i] Kontrola indexov Zaberanie miesta Ľahké a lacné vkladanie Usporiadanie -91919.9 Vyhľadávanie LIFO, FIFO, Protekcia Prechádzanie Duplicita Vyhadzovanie
Výhody a nevýhody pola double *pole = new double[size]; 4.32 88.21 Výhody: jednoduchosť, pochopiteľnosť rýchly priamy prístup ku všetkým položkám úplná kontrola nad všetkými činnosťami Nevýhody: hrôzostrašné nebezpečenstvo prekročenia limitov pola veľká cena zmeny veľkosti veľká cena vsúvania dát do vnútra úplná kontrola nad všetkými činnosťami 6.21 1222.1 0.0 3.14 4.32
STL C++ ponúka šablónové riešenie pre manipuláciu s dátami rovnakého typu: knižnicu STL (Standard Template Library). Sekvenčné kontajnery lineárne organizované kontajnery (vector, list, deque, bitset). Asociatívne kontajnery sami si určujú spôsob uloženia. Umožňujú prístup prostredníctvom kľúčov (set, multiset, map, multimap).
Sekvenčné kontajnery Sekvenčné kontajnery lineárne organizované kontajnery, u ktorých má zmysel hovoriť o poradí uložených dát. Dynamicky menia svoju veľkosť podľa potreby. vector je lineárna postupnosť objektov. deque je to fronta s dvoma koncami. list je to obojstranný zreťazený zoznam.
Kontajner - vector vector pripomína C++ pole. Ukladá objekty do súvislého pola objektov. Je optimalizovaný pre náhodný výber. 4.32 Umožňuje náhodný prístup k ľubovoľnej položke pomocou preťaženého operátora[] alebo metódy at(). Nové objekty sa štandardne vkladajú na koniec. v.push_back(8.12); 88.21 6.21 1222.1 0.0 3.14 4.32 8.12 Vsúvanie objektu inde, ako na koniec, je možné, ale je drahé.
Konštruktory kontajnera vector vector<type> name; vector<type> name(size, value); vector<type> name(size); vector<type> name(sourcevect); vector<type> name(prvy, posl); Vytvorí prázdny vektor. Vytvorí vektor veľkosti size. Nastaví všetky položky na value. Vytvorí vektor veľkosti size s preddefinovanými hodnotami. Vytvorí kópiu vektora sourcevect rovnakého typu. Skopíruje elementy zdrojového vektora od iterátora prvy po iterátor posl.
Alokovanie pamäti pre vector vector pri svojom vzniku alokuje toľko priestoru, koľko si myslí, že budete potrebovať. Veľkosť môžete zadať priamo v konštruktore alebo použitím metódy reserve(int). vector<typ> v(100); v.reserve(1000); Alokuje 100 prvkov Alokuje 1000 prvkov vector alokuje vždy súvislý kus pamäti. Ak sa alokovaný priestor vyčerpá a vkladá sa ďalší objekt: 1. alokuje sa väčší priestor 2. skopírujú sa všetky objekty do nového priestoru (pomocou kopírovacích konštruktorov) 3. zrušia sa všetky staré objekty (pomocou ich deštruktorov) 4. uvoľní sa stará pamäť
Metódy kontajnera vector intvector.size(); intvector.capacity(); Počet uložených údajov vo vektore Kapacita aktuálnej veľkosti vektora intvector[i]; Vracia referenciu i-ej položky (od 0) intvector.at(i); intvector.push_back(int); intvector.insert(pos,v); Vracia referenciu a vyhodí výnimku v prípade zlého indexu Pridá na koniec novú položku Vsunie novú položku na určenú pozíciu intvector.pop_back(); intvector.erase(pos); Zruší poslednú položku Zruší položku(y) určenú iterátorom intvector.clear(); intvector.empty(); Zruší všetky položky vektora. Vracia true, ak je vektor prázdny
Vkladanie položiek Pri vkladaní položiek do kontajnerov sa privoláva ich kopírovací konštruktor. class POLOZKA { string value; public: POLOZKA(string val = "") : value(val) { cout << "Normálny konstruktor = " << " " << value << endl; ; POLOZKA(const POLOZKA &p): value(p.value) { cout << "Kopirovaci konstruktor = " << " " << value << endl; ; string getvalue() { return value; ; ; int main() { POLOZKA a("prva"), b("druha"), c("tretia"); vector<polozka> polozky; polozky.push_back(c); polozky.insert(polozky.begin(), b); polozky.push_back(a); cout << "Polozky: "; for (unsigned int i = 0; i < polozky.size(); i++){ cout << polozky[i].getvalue() << " "; Záhada na premýšľanie: Prečo sa dvakrát volá kopírovací konštruktor pre Tretia
Zlý index #include <iostream> #include <vector> using namespace std; int main() { vector<int> v(10, 11); cout << "v[3] = " << v[3] << endl; cout << "v[33] = " << v[33] << endl; cout << "v[-1] = " << v[-1] << endl; cout << "v[-2] = " << v[-2] << endl; v[-2] = 99; v[-12] = 12; cout << "v[-200] = " << v[-200] << endl; cout << "v[-2] = " << v[-2] << endl; cout << "v[-12] = " << v[-12] << endl; cout << "v.at(-12) = "; cout << v.at(-12) << endl;
Kontajner deque deque je sekvenčný kontajner predstavujúci obojstrannú frontu. Umožňuje rýchly náhodný prístup k ľubovoľnej položke pomocou preťaženého operátora[]. Je optimalizovaný pre vkladanie a odoberanie objektov z oboch koncov. dq.push_front(i); dq.push_back(i); Zdroj: Wikipedia
Príklad pre deque #include <iostream> #include <deque> using namespace std; int main() { deque<int> dq; for (int i = 0; i < 6; i++){ dq.push_front(i); dq.push_back(i); for (int j = 0; j < dq.size(); j++) { cout << dq[j] << " ";
Porovnanie efektivity Porovnanie efektivity pri spracovaní 1.7 MB textového súboru. Vpisovanie do kontajneru vector: Vpisovanie do kontajneru deque: 92% - rýchlejší Používanie iterátoru pre vector: Používanie iterátoru pre deque : 105% - pomalšie Niekedy je výhodnejšie použiť pri načítavaní dát kontajner deque a potom ho prekopírovať do kontajnera vector.
Kontajner - list list je obojsmerný zreťazený zoznam. Vhodný pre rýchle vkladanie objektu kdekoľvek, čo sú pre vector aj deque drahé operácie. Je drahé sa v ňom náhodne pohybovať. Zdroj: Wikipedia Neumožňuje náhodný prístup pomocou operator[].
Vstavané metódy pre list charlist.reverse(); charlist.sort(); charlist.unique(); charlist.remove('f'); charlist.merge(charlist2); Vymení poradie položiek v zozname Usporiada položky Odstráni duplikáty, ak sú vedľa seba Odstráni všetky prvky 'f' Spojí dva zoznamy. Ak sú rovnako utriedené, vytvorí utriedený zoznam z oboch.
Príklad pre list #include <iostream> #include <list> using namespace std; int main() { list<char> charlist; for (int i=0; i < 5; i++){ charlist.push_back('e' + i); charlist.push_front('e' - i); list<char>::iterator iter; for (iter=charlist.begin(); iter!= charlist.end(); iter++){ cout << *iter; charlist.reverse(); cout << endl << "Opacne poradie: "; for (iter=charlist.begin(); iter!= charlist.end(); iter++){ cout << *iter; charlist.remove('f'); cout << endl << "Odstranene 'f': "; for (iter=charlist.begin(); iter!= charlist.end(); iter++){ cout << *iter; charlist.unique(); cout << endl << "Odstranene duplicity: "; for (iter=charlist.begin(); iter!= charlist.end(); iter++){ cout << *iter; charlist.sort(); cout << endl << "Usporiadanie: "; for (iter=charlist.begin(); iter!= charlist.end(); iter++){ cout << *iter; list<char> charlist1(charlist); charlist.merge(charlist1); cout << endl << "Spojenie: "; for (iter=charlist.begin(); iter!= charlist.end(); iter++){ cout << *iter;
Spoločné metódy Kontajnery sa snažia mať podobné rozhrania (pokiaľ to ich typ povoľuje). Všetky sekvenčné kontajnery vector, deque, list majú metódu: push_back(t); Vloží nový objekt T na koniec Kontajnery deque, list majú metódu na vkladanie na začiatok: push_front(t); Vloží nový objekt T na začiatok Kontajnery vector, deque majú metódu na priamy prístup : operator[i] Priamy prístup
Výber najvhodnejšieho kontajnera Pravidlo správneho nástroja Výber správneho kontajnera sa riadi jeho účelom, nie sympatiou. Pravidlo jednoduchosti Ak nemáte špeciálny dôvod na výber kontajneru, použite vector. Vector: Je najintuitívnejšie pochopiteľný zo všetkých kontajnerov. Objekty, ktoré sú vedľa seba v kontajneri, sú vedľa seba aj v pamäti. Poskytuje najrýchlejší prístup ku všetkým elementom kontajneru. Má najmenšiu réžiu.
Adaptéry Adaptéry stack, queue, priority_queue nie sú samostatné kontajnery, ale upravujú si (adaptujú) existujúce kontajnery vector, list a deque. Nemôžete v nich použiť metódy kontajnerov, ktoré adaptujú. Používajú svoje vlastné metódy. Adaptér stack sa správa ako zásobník LIFO (last in, first out). Adaptér queue sa správa ako fronta FIFO (first in, first out). Adaptér priority_queue sa správa ako usporiadaný zásobník.
Metódy adaptéra stack Konštruktory: stack<type> zasob; stack<type, vector<type> >zasob; stack<type, list<type> > zasob; Vytvorí prázdny stack z deque Vytvorí prázdny stack z vector Vytvorí prázdny stack z list Metódy (pre int): const int & top(); void push(const int &); void pop(); bool empty(); int size(); Referencia na posledný element. Vloží na koniec nový element. Zruší posledný element. Vracia true, ak je adaptér prázdny Veľkosť adaptéra.
Príklad pre adaptér stack #include <iostream> #include <list> #include <stack> using namespace std; int main(){ stack<int, list<int> > intstack; cout << "Vkladanie:" << endl; for (int x = 0; x < 5; x++) { intstack.push((x + 1)*100); cout << intstack.top() << endl; cout << "Vyber:" << endl; while(intstack.size()){ cout << intstack.top() << endl; intstack.pop();
Konštruktory: Metódy adaptéra queue Adaptér queue sa správa ako fronta FIFO (first in, first out). Nemôže sa preto vytvoriť z kontajnera vector. queue<type> fronta; queue<type, list<type> > fronta; Vytvorí prázdny queue z deque Vytvorí prázdny queue z list Metódy (pre int): const int & front(); void push(const int &); void pop(); bool empty(); int size(); Referencia na prvý element. Vloží na koniec nový element. Zruší prvý element. Vracia true, ak je adaptér prázdny Veľkosť adaptéra.
Príklad pre adaptér queue #include <iostream> #include <queue> using namespace std; int main(){ queue<int> intqueue; cout << "Vkladanie:" << endl; for (int x = 0; x < 5; x++) { intqueue.push((x + 1)*100); cout << (x + 1)*100 << endl; cout << "Vyber:" << endl; while(intqueue.size()){ cout << intqueue.front() << endl; intqueue.pop();
Adaptér priority_queue Adaptér priority_queue sa správa ako usporiadaný zásobník. Čítať možno iba najvyšší element, ale metóda push nevkladá nový element na koniec, ale ho uloží podľa usporiadajúcej funkcie. Implicitná šablóna je less, ale je možné si definovať aj svoju vlastnú. Pre adaptér priority_queue je preddefinovaný kontajner vector.
Metódy priority_queue Konštruktory: priority_queue<int> pqueue; priority_queue<int, deque<int>, greater<int> > pqueue; Metódy (pre int): const int & top(); void push(const int &); void pop(); bool empty(); int size(); Usporiadaná fronta int. Kontajner je vector. Predikát je less. Usporiadaná fronta int. Kontajner je deque. Predikát je greater. Referencia na najvyšší element. Vloží podľa usporiadania element. Zruší najvyšší element. Vracia true, ak je adaptér prázdny Veľkosť adaptéra.
Predikát pre priority_queue template<class T> class compare { public: bool operator()(t x, T y) { return x < y; ; priority_queue<int, vector<int>, compare<int> > pqueue; V šablónovej triede compare definujete preťažený operator(), ktorý vracia true, ak je podmienka predikátu splnená. Predikát je compare
Priority queue príklad #include <iostream> #include <queue> using namespace std; template<class T> class Greater{ Predikát Greater public: bool operator()(t x, T y) { return x < y; ; int main(){ priority_queue<int, deque<int>, Greater<int> > priorqeueu; priorqeueu.push(30); priorqeueu.push(10); priorqeueu.push(40); priorqeueu.push(35); priorqeueu.push(22); cout << "Usporiadana fronta:" << endl; int size = priorqeueu.size(); for (int x = 0; x < size; x++) { cout << priorqeueu.top() << endl; priorqeueu.pop();
Iterátory iterator niečo ako špecializovaný smerník na položky daného kontajnera. Umožňuje programu pohybovať sa v kontajneri pomocou iterátorovej aritmetiky, pristupovať k jednotlivým položkám a manipulovať nimi. Je niekoľko typov iterátorov: <typ_kontajneru>::iterator <typ_kontajneru>::reverse_iterator <typ_kontajneru>::const_iterator <typ_kontajneru>::istream_iterator <typ_kontajneru>::ostream_iterator Štandardný iterátor Cúvajúci iterátor Konštantný iterátor, neumožňuje meniť položky, na ktoré ukazuje Načítavajúci iterátor, umožňujúci iba jeden prechod Zapisujúci iterátor, umožňujúci iba jeden prechod
Použitie iterátorov deque<int>::iterator iq; *iq (*iq).f() iq->f() Objekt, na ktorý iterátor ukazuje Metóda objektu, na ktorý iterátor ukazuje Metóda objektu, na ktorý iterátor ukazuje
Metódy, ktoré vracajú iterátory iter = vector.begin(); iter = vector.end(); iter = vector.rbegin(); iter = vector.rend(); iter = vector.insert(poz,v); Vracia iterátor na začiatok kontajnera Vracia iterátor na koniec kontajnera Vracia iterátor na reverzný začiatok Vracia iterátor na reverzný koniec Vracia iterátor na vložený prvok
Príklad na iterátory #include <iostream> #include <vector> using namespace std; int main() { vector<int> intvector(5); vector<int>::iterator iter = intvector.begin(); *iter++ = 7; *iter++ = 2; *iter++ = 10; *iter++ = -6; *iter++ = 20; cout << "Polozky vektora: " << endl; for (iter = intvector.begin(); iter < intvector.end(); iter++){ cout << *iter << " ";
Chyby: pretečenie iterátora #include <iostream> #include <vector> using namespace std; const int MAX = 5; int main() { vector<int> intvector(max); vector<int>::iterator out = intvector.begin(); for(int i = 0; i < MAX + 1; i++, out++){ *out = i * 10; cout << "Polozky vektora: " << endl; vector<int>::iterator it = intvector.begin(); while (it!= intvector.end()){ cout << *it++ << endl; Pretečenie Iterátora.
Chyby: zmena kapacity #include <iostream> #include <vector> using namespace std; const int MAX = 5; int main() { vector<int> intvector(max); vector<int>::iterator prvy = intvector.begin(); for(unsigned int i = 0; i < MAX + 1; i++){ intvector[i] = i * 10; cout << *prvy << endl; Po zmene kapacity vektora prestáva platiť nastavenie iterátora prvy.
Chyby: neplatná hodnota #include <iostream> #include <vector> using namespace std; int main() { vector<int> intvector(5, 1); cout << "Polozky vektora: " << endl; vector<int>::iterator it = intvector.begin(); for (int i = 0; i < intvector.size(); i++){ intvector.insert(it, 55); cout << *it++ << endl; Program padne, pretože po insert stratil iterátor it správnu hodnotu. it = intvector.insert(it,55); Správna verzia:
Chyby: zlý rozsah od do for_each(intvector.end(), intvector.begin(), nieco); Vymenené poradie iterátorov for_each(intvector.begin(), intvector2.end(), nieco); Iterátory ukazujú do rôznych kontajnerov.