Итераторы
Итератор — это объект, который позволяет перебирать элементы в контейнере и предоставлять доступ к отдельным элементам. Итераторы обеспечивают абстракцию, позволяющую работать с контейнерами универсальным образом, не завися от их конкретной реализации.
std::string s = "123";
std::string::iterator s_it = s.begin();
std::list<int> l = {1, 2, 3};
std::list<int>::iterator l_it = l.begin();
std::vector<int> v = {1, 2, 3};
std::vector<int>::iterator v_it = v.begin();
Виды итераторов
Есть разделение по двум категориям:
- прямой и обратный — указывает порядок прохода по элементам; например для обратного итератора операция инкремента смещает оператор в ближе к началу
- изменяемый и константный — разрешает или запрещает изменять элементы, на которые указывает итератор
Интерфейс
Использование итераторов очень похоже на использование указатей.
Разыменование
operator*— возвращает ссылку на текущий элемент, на который указывает итераторoperator->— позволяет использовать итератор, как если бы он был указателем на объект
struct Point { float x; float y; };
std::vector<Point> v = {{0, 0}, {1, 1}};
auto it = v.rbegin();
*it = {0, 2};
it->x = 2;
Инкремент и декремент:
operator++— перемещает итератор на следующий элемент; префиксный инкремент возвращает измененный итератор, а постфиксный возвращает копию итератора перед изменениемoperator--— перемещает итератор на предыдущий элемент; префиксный возвращает измененный итератор, а постфиксный возвращает копию итератора перед изменением
int i;
std::vector<int> v = {1, 2, 3};
auto it = v.begin();
++it;
i = *it; // i == 2
--it;
i = *it; // i == 1
Если возвращаемое значение игнорируется, следует использовать префиксную
запись (++it вместо it++), т.к. она не создаёт копии.
Переход к произвольному элементу:
operator+иoperator-— возвращают новый итератор, смещённый на указанное количество позицийoperator+=иoperator-=— смещают итератор на указанное количество позиций
int i;
std::vector<int> v = {1, 2, 3};
auto it = v.cbegin();
it += 2;
i = *it; // i == 3
i = *(it - 2); // i == 1
Выражение *(it - 2), как и для указателя, может быть записано как it[-2].
Сравнение:
operator==— возвращаетtrue, если интераторы указывают на один и тот же элементoperator!=— возвращаетtrue, если интераторы указывают на разные элементы
STL
Большинство контейнеров стандартной библиотеки предоставляют итераторы для навигации по своим элементам и имеют одинаковый интерфейс:
- итераторы:
beginиend - константные итераторы:
cbeginиcend - обратные интераторы:
rbeginиrend - константные обратные итераторы:
crbeginиcrend
Здесь "c" означает "const", а "r" — "reverse".
std::list<int> l = {1, 2, 3};
// iterator
l.begin(); l.end();
// const iterator
l.cbegin(); l.cend();
// reverse iterator
l.rbegin(); l.rend();
// const reverse iterator
l.crbegin(); l.crend();
Методы вида begin указывают на первый (с учётом направления) элемент
в контейнере, а end — на элемент, "следующий за последним". Это нужно
для организации цикла.
for (auto it = l.begin(); it != l.end(); ++it) {
// forward direction
}
for (auto rit = l.rbegin(); rit != l.rend(); ++rit) {
// backward direction
}
Класс итератора сам по себе, без использования этих методов, доступен
через *::iterator; например для контейнера std::map<int, int> класс
итератора доступен как std::map<int, int>::iterator.
Range-base loop
Запись вида
for (auto it = container.begin(); it != container.end(); ++it) {
auto& element = *it;
// some actions with the element
}
может быть упрощена до вида
for (auto& element : container) {
// some actions with the element
}
и называется range-based loop.