Это красивое и короткое выражение, названное «основанный на диапазоне цикл for
{
auto && range = array_or_vector_or_map_or_list;
auto begin = std::begin( range);
auto end = std::end( range);
for ( ; begin != end; ++ begin) {
int i = * begin;
sum += i;
}
}
Такие циклы хорошо знакомы всем, кто уже работал с итераторами, но кажутся черной магией для тех, кто еще этого не делал. Представьте, что наш вектор, содержащий целые числа, выглядит следующим образом (рис. 3.1).
Команда std::begin(vector)
vector.begin()
, она возвращает итератор, который указывает на первый элемент (1
). Команда std::end(vector)
аналогична команде vector.end()
, она возвращает итератор, указывающий на элемент, стоящий 5
).На каждой итерации цикл проверяет, равен ли начальный итератор конечному. Если это не так, то мы
Категории итераторов
Существует несколько категорий итераторов, каждая из которых имеет разные ограничения. Их несложно запомнить, однако имейте в виду: возможности, требуемые одной категорией, унаследованы из другой, более мощной. Вся суть категорий итераторов заключается в том, что если при реализации алгоритма вы знаете, с каким именно итератором будете работать, то сможете реализовать оптимизированную версию. Таким образом, программисту достаточно просто выразить свое намерение, а компилятор выберет
Рассмотрим их в правильном порядке (рис. 3.2).
Итераторы ввода
Могут быть разыменованы только для того, чтобы
std::istream_iterator
.Однонаправленные итераторы
Аналогичны итераторам ввода, но отличаются тем, что по диапазонам данных, которые они представляют, вы можете проитерировать несколько раз. В качестве примера такого итератора приведем std::forward_list
Двунаправленные итераторы
Как следует из названия, их можно инкрементировать и декрементировать, что позволяет итерировать вперед и назад. Эту возможность, например, поддерживают итераторы для контейнеров std::list
std::set
и std::map
.Итераторы с произвольным доступом
Позволяют перескакивать через несколько значений сразу вместо того, чтобы двигаться пошагово. Примером таких итераторов являются итераторы для контейнеров std::vector
std::deque
.Непрерывные итераторы
Соответствуют всем указанным выше требованиям, но при этом необходимо, чтобы данные, по которым выполняется итерирование, находились в непрерывной памяти, как, например, в массиве std::vector
Итераторы вывода
Вынесены в отдельную категорию. Причина такова: итератор может быть чистым итератором вывода, который можно только инкрементировать и использовать для
Изменяемые итераторы
Если итератор является итератором вывода, а также состоит в какой-то другой категории, то называется изменяемым. С его помощью можно считывать и записывать данные. При получении из неконстантного экземпляра контейнера итератор, как правило, будет состоять именно в этой категории.
Создаем собственный итерабельный диапазон данных
Мы уже знаем, что итераторы в некоторой степени представляют собой
++
, оператор разыменования *
и оператор сравнения объектов ==
, и получится примитивный итератор, подходящий для работы с циклом for
, основанным на диапазоне, который появился в C++11.