Это красивое и короткое выражение, названное «основанный на диапазоне цикл for
», существует еще со времен С++11. Оно представляет собой лишь синтаксический сахар, который развертывается в нечто похожее на следующий код:
{
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.