|
| 1 | +# Vector |
| 2 | + |
| 3 | +Vector je kontainer, ktorý je súčasťou štandardnej knižnice C++ a poskytuje intedrface dynamického pola. To znamená, že `std::vector` nám umožňuje ukladať a spravovať dáta vo forme poľa, pričom veľkosť tohto poľa môžete meniť počas behu programu. Vector patrí k základným a najpoužívanejším dátový štruktúram vôbec. |
| 4 | + |
| 5 | +`std::vector` sídli v hlavičkovom súbore `#include <vector>`. |
| 6 | + |
| 7 | +*Funkcie, ktoré sa ďalej budú spomínať majú veľa overloadov, všetky neuvádzame, dajú sa ľahko nájsť v dokumentácií.* |
| 8 | + |
| 9 | +Niektoré dôležité vlastnosti a operácie, ktoré sú spojené s `std::vector`: |
| 10 | + |
| 11 | +1. **Dynamická veľkosť**: Hlavnou výhodou `std::vector` je schopnosť meniť svoju veľkosť počas behu programu. Môžeme pridávať a odoberať prvky podľa potreby, čo je veľmi užitočné, ak nevieme presne, koľko prvkov budeme potrebovať vopred, ako to potrebuje pri statických poliach (e.g. `int[]`). |
| 12 | + |
| 13 | +2. **Rýchly prístup** Prístup k prvkom `std::vector` je rýchly, pretože vnútorné dáta sú usporiadané v pamäti ako kontinuálny blok, čo umožňuje použiť smerníkovú aritmetiku aby sme sa rýchlo dostali k prvku, ktorý nás zaujíma. Pre prístup k prvku môžeme použiť operátor `[]` rovnako ako pri bežných poľových dátových štruktúrach. |
| 14 | + |
| 15 | +3. **Automatická správa pamäte**: `std::vector` spravuje svoju pamäť automaticky. To znamená, že sa stará o alokáciu a dealokáciu pamäte, čo nás zbavuje starostí s ručnou správou pamäte. |
| 16 | + |
| 17 | +4. **Iterátory**: Môžeme použiť interface iterátorov na prechádzanie a manipuláciu s prvkami v `std::vector`. |
| 18 | + |
| 19 | +## Vytváranie |
| 20 | + |
| 21 | +### Vytvorenie prázdneho vektora |
| 22 | + |
| 23 | +Ak chcete vytvoriť prázdny vektor a neskôr doň pridávať prvky, jednoducho vytvorte vektor bez uvedenia počiatočných hodnôt. |
| 24 | + |
| 25 | +```cpp |
| 26 | +std::vector<int> numbers; |
| 27 | +``` |
| 28 | + |
| 29 | +### Inicializácia vektora s určenou veľkostou |
| 30 | + |
| 31 | +Môžeme inicializovať vektor s určitým počtom prvkov, ktoré majú počiatočnú hodnotu. Nato použijeme konštruktor s dvoma argumentami - počtom prvkov a ich počiatočnou hodnotou. *Pozor toto nie je iba predalokovanie miesta.* |
| 32 | + |
| 33 | +```cpp |
| 34 | +std::vector<int> numbers(5, 1); // Vektor s 5 jednotkami |
| 35 | +``` |
| 36 | +
|
| 37 | +### Inicializácia vektora z existujúceho poľa pomocou iterátorov |
| 38 | +
|
| 39 | +Môžeme inicializovať vektor na základe existujúceho poľa alebo iného vektora. Na to môžete použiť konštruktor `std::vector` s dvoma iterátormi ukazujúcimi na začiatok a koniec rozsahu, ktorý chceme skopírovať. |
| 40 | +
|
| 41 | +```cpp |
| 42 | +int array[] = {1, 2, 3, 4, 5}; |
| 43 | +std::vector<int> numbers(array + 1, array + 5); // 2, 3, 4, 5 |
| 44 | +
|
| 45 | +std::vector<int> numbers2(numbers.begin(), numbers.end() - 1); // 2, 3, 4 |
| 46 | +``` |
| 47 | + |
| 48 | +### Inicializácia vektora pomocou zoznamu inicializácie |
| 49 | + |
| 50 | +Od C++11 môžeme vytvoriť a inicializovať vektor pomocou zoznamu inicializácie *(angl. initializer list)*. |
| 51 | + |
| 52 | +```cpp |
| 53 | +std::vector<int> numbers = {1, 2, 3, 4, 5}; |
| 54 | +``` |
| 55 | +
|
| 56 | +## Prechádzanie cez vektor |
| 57 | +
|
| 58 | +Na iteráciu vektora používame funkcie `begin()` a `end()`. Tieto funkcie poskytujú iterátory na začiatok a koniec kontajnera, čo umožňuje prechádzať jeho prvky pomocou klasických `for`-cyklusov. Alebo môžeme využiť automatické rozsahé for-cyklusov (*range-based for loops*). Oba spôsoby sú OK, akurát druhý je modernejší. |
| 59 | +
|
| 60 | +### Pomocou `begin()` a `end()` a klasického `for`-cyklu |
| 61 | +
|
| 62 | +```cpp |
| 63 | +std::vector<int> numbers = {1, 2, 3, 4, 5}; |
| 64 | +
|
| 65 | +for (std::vector<int>::iterator it = numbers.begin(); it != numbers.end(); ++it) { |
| 66 | + std::cout << *it << " "; // výpis: 1 2 3 4 5 |
| 67 | +} |
| 68 | +``` |
| 69 | + |
| 70 | +### Pomocou rozsahového for-cyklu |
| 71 | + |
| 72 | +Rozsahový for-cyklus umožňuje jednoduchšiu iteráciu vektora bez priameho použitia iterátorov. Aj keď pod kapotou sa používajú iterátory a teda oba spôsoby iterácie sú viacmenej ekvivalentné. |
| 73 | + |
| 74 | +```cpp |
| 75 | +std::vector<int> numbers = {1, 2, 3, 4, 5}; |
| 76 | + |
| 77 | +for (int num : numbers) { |
| 78 | + std::cout << num << " "; // výpis: 1 2 3 4 5 |
| 79 | +} |
| 80 | +``` |
| 81 | + |
| 82 | +## Pridávanie prvkov |
| 83 | + |
| 84 | +### Na koniec |
| 85 | + |
| 86 | +Pridávanie na koniec `std::vector` sa robí pomocou funkcie `push_back`. Operácia je veľmi rýchla O(1) v priemernom prípadne. Ak sa však veľkosť vektora približuje svojmu maximálnemu kapacitnému limitu, vektor môže vyžadovať alokáciu nového bloku pamäte a kopírovanie existujúcich prvkov do nového bloku. Toto môže mať lineárnu časovú zložitosť O(n), kde n je aktuálna veľkosť vektora. |
| 87 | + |
| 88 | +Vektor si teda zvyčajne drží väčší blok pamäte ako reálne potrebuje. Preto väčšinou má miesto na ešte jeden prvok. Ak ale zrovna nemá, tak je pridanie drahé. Stratégie ako to vektor robí sa líša od vendora štandardnej knižnice. Zvyčajne sa veľkosť vektora zdvojnásobuje. |
| 89 | + |
| 90 | +Povedzme, že máme vektor s 8 prvkami. Pridanie ďalšieho na koniec. Urobí realokáciu a 8 rvkov sa musí posununúť. Lenže teraz má vektor dvojnásobnú kapacitu, teda 16 prvkov, preto ďalších 7 pridaní na koniec nebude vyžadovať realokáciu. |
| 91 | + |
| 92 | +### Na začiatok a inde do vektora |
| 93 | + |
| 94 | +Funkcia `insert` má dva parametre. Prvý je pozícia, kam chceme vložiť, druhý je čo chceme vložiť. |
| 95 | + |
| 96 | +```cpp |
| 97 | +std::vector<int> numbers = {1, 2, 3, 4, 5}; |
| 98 | +numbers.insert(numbers.begin() + 2, 99); // 1, 2, 99, 3, 4, 5 |
| 99 | +``` |
| 100 | +
|
| 101 | +Ako vidíme, pozícia je určená iterátorom a nie indexom. To ale nie je problém, lebo stačí urobiť `vec.begin() + idx` a konvertujeme index `idx` na iterátor do vektora `vec`. |
| 102 | +
|
| 103 | +## Veľkosť a kapacita |
| 104 | +
|
| 105 | +V kontexte štandardného vektora v jazyku C++ je dôležité rozlišovať medzi veľkosťou a kapacitou vektora. Obe tieto vlastnosti sú dôležité pre správu dát vektora. Ich poznaním sa vieme vyhnúť nedefinovanému správaniu. |
| 106 | +
|
| 107 | +### Veľkosť (size) |
| 108 | +
|
| 109 | +Veľkosť vektora označuje počet prvkov, ktoré momentálne obsahuje. Na zistenie veľkosti vektora používate funkciu `size()`. |
| 110 | +
|
| 111 | +```cpp |
| 112 | +std::vector<int> numbers = {1, 2, 3, 4, 5}; |
| 113 | +size_t size = numbers.size(); // size obsahuje hodnotu 5 |
| 114 | +``` |
| 115 | + |
| 116 | +Veľkosť vektora sa automaticky mení, keď pridávate alebo odoberáte prvky. Ak ju chceme zmeniť explicitne, tak môžeme pomocou `resize(new_size)`, ak je nová veľkošt väčšia, tak sa nové prvky default inicializujú. |
| 117 | + |
| 118 | +### Kapacita (capacity): |
| 119 | + |
| 120 | +Kapacita vektora označuje maximálny počet prvkov, ktoré môže vektor obsahovať, bez potreby reálnej alokácie ďalšej pamäti. Kapacitu môžete zistiť pomocou funkcie `capacity()`. Kapacita sa môže zvýšiť počas operácie `push_back()` alebo iných operácií, ktoré pridávajú prvky do vektora. Štandardne sa kapacita nezmenšuje pri odoberaní prvkov. |
| 121 | + |
| 122 | +Keď je veľkosť vektora rovnaká ako kapacita, znamená to, že vektor je plný a ďalšie pridávanie prvkov môže viesť k reálnej alokácií pamäte, čo môže byť náročné na výkon. Preto v kritickych miestach je vhodné poznať kapacitu a podľa potreby ju meniť, aby sa minimalizovala realokácia pamäte. |
| 123 | + |
| 124 | +Ak chcete explicitne nastaviť kapacitu vektora, môžete použiť metódu `reserve()`. Znova vieme iba zväčšovať. Ak chceme kapacitu zmenšiť máme funkciu `shrink_to_fit()`, ktorá **by mala** presunuť vektor do bloku pamäte, ktorý presne zodpovedá jeho aktuálnym potrebám. |
| 125 | + |
| 126 | +```cpp |
| 127 | +std::vector<int> numbers; |
| 128 | + |
| 129 | +// explicitné rezervovanie kapacity pre 10 prvkov |
| 130 | +numbers.reserve(10); |
| 131 | + |
| 132 | +// teraz môžeme pridávať prvky |
| 133 | +for (int i = 1; i <= 10; i++) { |
| 134 | + numbers.push_back(i); // nikdy neurobi realokaciu |
| 135 | +} |
| 136 | +``` |
| 137 | + |
| 138 | +## Najčastejšie chyby |
| 139 | + |
| 140 | +Vektor is síce extrémne rýchly, ale táto rýchlost je dosiahnutá velkým množstvom nedefinovaného správania. |
| 141 | + |
| 142 | +### Operátor [] |
| 143 | + |
| 144 | +Operátor prístupu k prvkom nekontroluje či pristupujeme správne, takže sa nám môže stať, že čitame neinicializovanú pamäť, alebo ešte horšie spôsobime porušenie ochrany pamäte. |
| 145 | + |
| 146 | +```cpp |
| 147 | +std::vector<int> a = { 1, 2, 3, 4, 5}; |
| 148 | + |
| 149 | +std::cout << a[5]; // garbage |
| 150 | + |
| 151 | +a[10000] = 0; // access violation or segmentation fault |
| 152 | +``` |
| 153 | +
|
| 154 | +### Neplatné iterátory |
| 155 | +
|
| 156 | +Iterátory sa zneplatia, keď prvok na ktorý ukazujú sa z vektora odstráni. Rovnako sú zneplatnené aj iterátory za prvkom, ktorý mažeme. To je ešte celkom pochopiteľné, navyše ale iterátory prestávajú byť platné aj keď vektor urobí realokáciu. |
| 157 | +
|
| 158 | +```cpp |
| 159 | +std::vector<int> a = { 1, 2, 3, 4, 5}; |
| 160 | +
|
| 161 | +auto it = a.begin() + 1; // points to 2 |
| 162 | +
|
| 163 | +a.erase(a.end() - 1); |
| 164 | +// it still valid |
| 165 | +
|
| 166 | +a.erase(a.begin()); |
| 167 | +// it invalid |
| 168 | +``` |
| 169 | + |
| 170 | +Pri `erase` ešte pozor `vec.erase(vec.end())` je nedefinované správanie. |
| 171 | + |
| 172 | +```cpp |
| 173 | +std::vector<int> a = { 1, 2, 3, 4, 5}; |
| 174 | + |
| 175 | +auto it = a.begin() + 1; // points to 2 |
| 176 | + |
| 177 | +a.push_back(0); |
| 178 | +// it may be invalid |
| 179 | +``` |
| 180 | +
|
| 181 | +V predchádzajúcom príklade nie je úplne jasné, či je iterátor platný, alebo nie. Môžeme to zistiť, tak že skontrolujeme kapacitu. |
| 182 | +
|
| 183 | +```cpp |
| 184 | +std::vector<int> a = { 1, 2, 3, 4, 5}; |
| 185 | +
|
| 186 | +auto it = a.begin() + 1; // points to 2 |
| 187 | +
|
| 188 | +auto space_available = a.capacity() > a.size(); |
| 189 | +a.push_back(0); |
| 190 | +
|
| 191 | +if (space_available) { |
| 192 | + // it is valid |
| 193 | +} else { |
| 194 | + // it is invalid |
| 195 | +} |
| 196 | +``` |
| 197 | + |
| 198 | +### Modifikácia vektora počas iterácie |
| 199 | + |
| 200 | +```cpp |
| 201 | +std::vector<int> a = { 1, 2, 3, 4, 5}; |
| 202 | + |
| 203 | +for (auto i : a) { |
| 204 | + if (i % 2 == 1) { |
| 205 | + a.push_back(0); |
| 206 | + } |
| 207 | +} |
| 208 | +``` |
| 209 | + |
| 210 | +Mohli by sme sa domievať, že kód vyššie na konci nechá vektor naplnený hodnotami `1, 2, 3, 4, 5, 0, 0, 0` (jedna nula za každé nepárne číslo). Nie je tomu ale tak, lebo range based for cyklus používa iterátory a `push_back` ich môže zneplatniť. Preto kód vyššie môźe viesť k nedefinovanému správaniu. |
| 211 | + |
| 212 | +Vo všeobecnosti nie je dobrý nápad iterovať vektor a meniť počet prvkov. Niektoré použitia sú ale OK. Napríklad `erase` vo for cykle. Stačí si zapamätať, čo zneplatnuje iterátory a mali by ste byť OK. |
0 commit comments