Итератор — это объект, указывающий на элемент какого-то контейнера.
Итератор является абстракцией над концепцией указателей. Если указатели работают только с последовательными данными (массивами), то итераторы работают с любыми контейнерами — например со связными списками или деревьями поиска — причём в единообразном синтаксисе, что сильно помогает разработчикам библиотек избегать дублирования кода.
#Синтаксис
Чтобы получить элемент, на который указывает итератор it, необходимо воспользоваться оператором разыменования: *it. Если нужно перейти к следующему элементу надо использовать инкремент: ++it (постфиксного инкремента у итераторов нет).
У всех контейнеров есть какой-то первый и последний элемент. Итератор на первый элемент можно получить через a.begin(), а через a.end() можно получить итератор на некий фиктивный элемент, следующий последним. Таким образом, если проходить от a.begin() до a.end() не включительно, ты мы пройдём по всем элементам контейнера.
vector<int> a = {1, 2, 3, 4, 5};
// в типе итератора должна содержаться информация о контейнере
// поэтому в случае вектора интов это "vector<int>::iterator"
for (vector<int>::iterator it = a.begin(); it != a.end(); ++it)
cout << *it << endl;
// в современном C++ можно вместо него использовать "auto"
for (auto it = a.begin(); it != a.end(); ++it)
cout << *it << endl;
// также если нужно пройтись по всему массиву, можно сжать до такого
for (int x : a)
cout << x << endl;
// если нужно как-то изменить элемент, его можно передать по ссылке
for (int &x : a)
x *= 2;
for (x : a)
cout << x << endl;
for (const int &x : a)
cout << x << endl;
// (также можно вместо int писать auto)
// the initializer may be a braced-init-list
for (int x : {1, 2, 3, 4, 5})
cout << x << endl;
// the initializer may be an array
int b[] = {1, 2, 3, 4, 5};
for (int x : b)
cout << x << endl;
array<int, 5> c = {1, 2, 3, 4, 5};
for (int x : c)
cout << x << endl;
#Категории итераторов
Итераторы — очень важная часть интерфейса контейнера. Итераторы можно передавать в различные алгоритмы стандартной библиотеки, которым не обязательно знать про внутреннее устройство контейнера, но нужно знать про то, какие паттерны доступа к данным возможны.
Поэтому в зависимости от внутренней структуры, итераторы контейнера могут отвечать к одному из нескольких абстрактных классов с разными уровнями гарантий:
input_iterator, поддерживающий только операции разыменования и инкремента — причём даже без гарантии, что после того, как был произведён инкремент, его предыдущие значения остались валидными. Как можно догадаться из названия, используется для потокового ввода.forward_iterator, помимо предыдущего гарантирующий что итераторы на какой-то конкретный элемент можно инкрементировать сколько угодно раз не опасаясь, что они исчезнут (что позволяет их использовать в алгоритмах, проходящих по данным несколько раз).bidirectional_iterator, помимо предыдущего поддерживающий возможность декремента (it--) — то есть перехода к предыдущему элементу.random_access_iterator, помимо предыдущего поддерживающий возможность переходить к элементу, который находится на каком-то расстоянии $k$ —it + k,it - k,it += k,it -= k— и находить расстояние между позициями, на которые указывают два итератора: например, выражениеa - bвернет целое число — расстояние между двумя элементами коллекции, соответствующим итераторамaиb.
#Алгоритмы из STL
Например, итераторы std::vector относятся к random_access_iterator, и если вызвать функцию lower_bound из стандартной библиотеки, то она произведет бинарный поиск по элементам (предполагая, что они отсортированы в порядке неубывания):
vector<int> a = {1, 2, 3, 5, 8, 13};
// вернет 8
cout << *lower_bound(a.begin(), a.end(), 7) << endl;
Функция lower_bound возвращает итератор на первый элемент, не меньший заданного. Также есть upper_bound, возвращающий первый элемент, строго больший (в случае с int это то же самое, что найти lower_bound от x + 1).
Зная, что итераторы вектора поддерживают произвольный доступ, бинарный поиск будет работать за $O(n \log n)$, но для других структур это, возможно, будет не так.
Также по этой причине часто имеет смысл вместо сишных массивов использовать std::array, который является точно таким же массивом фиксированной длины, но поддерживает итераторы и вместе с ними все алгоритмы STL:
array<int, 3> a = {4, 2, 1, 3};
// вернет 1
cout << *min_element(a.begin(), a.end()) << endl;