Search
Výchozí kód
Náš první úkol bude zjednodušit implementaci některých speciálních členských funkcí, jmenovitě kopírujícího přiřazení, přesunujícího konstruktoru a přesunujícího přiřazení. Velmi nám pomůže, když budeme mít k dispozici metodu, která dvěma objektům typu vector vymění veškerý obsah.
vector
swap
void swap(vector& rhs)
std::swap
<utility>
Náš vector už je jen malý kousek od plnohodnotného std::vector<double>. Jedna nápadná chybějící funkcionalita je možnost použít operátor indexace pole pro přístup k jednotlivým prvkům vectoru. Chtěli bychom, aby následující kód fungoval:
std::vector<double>
void multiply_all(vector& v, double factor) { for (size_t i = 0; i < v.size(); i++) { v[i] *= factor; } } double accumulate(const vector& v) { double sum = 0; for (size_t i = 0; i < v.size(); i++) { sum += v[i]; } return sum; } vector cat(vector v) { auto original_size = v.size(); v.resize(v.size() * 2); for (size_t i = 0; i < original_size; ++i) { v[original_size + i] = v[i]; } return v; }
[]
double& operator[](size_t)
double operator[](size_t) const
at
Následující vychytávku std::vector neumí, ale bude se nám hodit. Protože je otravné psát print_vector vždy, když chceme vypsat obsah vectoru na výstup, vytvořme si vlastní přetížení operace <<.
std::vector
print_vector
<<
std::ostream& operator<<(std::ostream&, const vector&)
Pokud jsme všechno udělali správně, následující kód bude fungovat:
std::cout << "v1: " << v1; std::cout << "v2: " << v2; std::cout << "v3: " << v3; std::cout << "v4: " << v4;
Řešení
Iterátory jsou důležitou součástí C++, protože umožňují spolupráci kolekcí (jako je vector) s algoritmy ze standardní knihovny. Až přidáme iterátory do naší třídy, bude možné například seřadit její prvky vzestupně pomocí volání std::sort.
std::sort
Není iterátor jako iterátor; iterátory kategorizujeme podle toho, jak jsou mocné.
Každá kolekce poskytuje iterátory z jedné z těchto kategorií.
Pojďme zavést dopředný (forward) iterátor pro třídu vector. Začínáme dopředným iterátorem, protože je nejjednodušší. Umožňuje nám procházet prvky jedním směrem, typicky zepředu dozadu. Až budeme hotovi, následující kód bude fungovat:
void multiply_all(vector& v, double factor) { for (vector::iterator it = v.begin(); it != v.end(); it++) { *it *= factor; } } double accumulate(const vector& v) { double sum = 0; for (vector::const_iterator it = v.begin(); it != v.end(); it++) { sum += *it; } return sum; }
Aby předchozí kód pracoval, jak má, musí být splňeny tyto podmínky:
vector::iterator
vector::const_iterator
begin
end
iterator
const_iterator
Pro samotné dopředné iterátory (iterator i const_iterator) musí platit následující pravidla. Za předpokladu, že it je iterátor:
it
++it
it++
*it
it == it2
it != it2
it->foo()
foo
it->bar
bar
double
Finta: ukazatel splňuje všechny požadavky dopředného iterátoru. (Dokonce splňuje i všechny požadavky iterátoru s náhodným přístupem.) Proto stačí jen implementovat metody begin a end a jako iterátory jmenovat ukazatele:
class vector { public: ... using iterator = double*; using const_iterator = const double*; ... };
Na cvičení ale budeme alespoň iterator implementovat pracně, abychom si vyzkoušeli přetěžování operátorů.
Pokud vytváříme iterátor jako vlastní třídu, musíme nějakým způsobem deklarovat jeho kategorii. To se dělá tak, že v něm zavedeme alias typu iterator_category. Není to ale jediná věc, kterou by o sobě měl iterátor deklarovat, celkem je potřeba zavést 5 aliasů typů:
iterator_category
std::forward_iterator_tag
difference_type
std::ptrdiff_t
it2
value_type
reference
double&
pointer
double*
class vector { public: ... class iterator { public: using difference_type = std::ptrdiff_t; using iterator_category = std::forward_iterator_tag; using value_type = double; using reference = double&; using pointer = double*; ... }; ... };
Obousměrný iterátor nám dovoluje při procházení couvat. Toho můžeme využít třeba k tomu, abychom prošli vector pozpátku. Chtěli bychom, aby fungoval tento kód:
#include "vector.hpp" #include <iostream> #include <algorithm> vector reverse(const vector& v) { if (v.size() == 0) return vector(); vector result; auto i = v.end(); do { i--; result.push_back(*i); } while (i != v.begin()); return result; } int main() { vector v; v.push_back(415); v.push_back(154); v.push_back(336); v.push_back(276); std::cout << v; // 415 154 336 276 std::reverse(v.begin(), v.end()); std::cout << v; // 276 336 154 415 v = reverse(v); std::cout << v; // 415 154 336 276 }
Zde používáme standardní algoritmus std::reverse (z hlavičky <algorithm>), který obrátí pole. Potom pole podruhé obrátíme vlastní funkcí reverse.
std::reverse
<algorithm>
reverse
Pro obousměrný iterátor platí následující dodatečné podmínky:
--it
it--
Třída obousměrného iterátoru oznamuje svou kategorii pomocí std::bidirectional_iterator_tag.
std::bidirectional_iterator_tag
Nejvyšší kategorií iterátoru, kterou si dnes ukážeme, je iterátor s náhodným přístupem. Ten umožňuje nejen procházet prvky, ale také provádět libovolně dlouhé skoky vpřed i vzad. Pouze kolekce s takovýmto iterátorem lze setřídit pomocí std::sort.
Následující kód by měl fungovat:
#include "vector.hpp" #include <iostream> #include <algorithm> vector::iterator binary_find(vector::iterator first, vector::iterator last, double val) { auto l = first; auto r = last; while (true) { if (l >= r) return last; // return last if val not found size_t sz = r - l; auto mid = l + sz / 2; if (*mid < val) { l = mid + 1; } else if (*mid > val) { r = mid; } else { return mid; } } } int main() { vector v; v.push_back(823); v.push_back(133); v.push_back(408); v.push_back(802); v.push_back(653); v.push_back(451); v.push_back(239); v.push_back(883); v.push_back(314); v.push_back(616); v.push_back(709); v.push_back(114); std::sort(v.begin(), v.end()); std::cout << v; auto it = binary_find(v.begin(), v.end(), 451); size_t pos = it - v.begin(); std::cout << "v[" << pos << "] = " << *it << '\n'; it = std::lower_bound(v.begin(), v.end(), 451); pos = it - v.begin(); std::cout << "v[" << pos << "] = " << *it << '\n'; }
K binárnímu vyhledávání lze použít i funkci standardní knihovny std::lower_bound, která funguje podobně1) jako naše binary_search. std::binary_search pak pouze vrátí true pokud byla hledaná hodnota nalezena. Existuje i std::upper_bound, která se chová trochu jinak a vrací iterátor na prvek, který je bezprostředně větší než hledaný prvek.
std::lower_bound
binary_search
std::binary_search
true
std::upper_bound
Za předpokladu, že it je iterátor a sz je velikost (typu size_t), dodatečné podmínky pro iterátor s náhodným přístupem jsou:
sz
size_t
it + sz
it - sz
it += sz
it -= sz
it < it2
>
<=
>=
it[sz]
*(it + sz)
it2 - it
Třída iterátoru s náhodným přístupem oznamuje svou kategorii pomocí std::random_access_iterator_tag.
std::random_access_iterator_tag