p ! = cont.end(); ++p) {
cout << *p << endl;
}
}
В этой ситуации следует использовать именно const
const_iterator
позволит компилятору не дать вам изменить *p
.Время от времени вам также может потребоваться перебирать элементы контейнера в обратном порядке. Это можно сделать с помощью обычного iterator
reverse_iterator
, который предназначен специально для этой задачи. reverse_iterator
ведет себя точно так же, как и обычный iterator
, за исключением того, что его инкремент и декремент работают противоположно обычному iterator
и вместо использования методов begin
и end
контейнера с ним используются методы rbegin
и rend
, которые возвращают reverse_iterator
. reverse_iterator
позволяет просматривать последовательность в обратном порядке. Например, вместо инициализации reverse_iterator
с помощью begin
он инициализируется с помощью rbegin
, который возвращает reverse_iterator
, указывающий на последний элемент последовательности. operator++
перемещает его назад — по направлению к началу последовательности, rend
возвращает reverse_iterator
, который указывает на элемент, находящийся перед первым элементом. Вот как это выглядит.for (list
p != lstStr.rend(); ++p) {
cout << *p << endl;
}
Но может возникнуть ситуация, когда использовать reverse_iterator
iterator
, как здесь.for (list
p != --lstStr.begin(); --p) {
cout << *p << endl;
}
Наконец, если вы знаете, на сколько элементов вперед или назад следует выполнить перебор, используйте вычисление значения, на которое следует перевести итератор. Например, чтобы перейти в середину списка, сделайте вот так.
size_t i = lstStr.size();
list
p += i/2; // Переход к середине последовательности
Но помните: в зависимости от типа используемого контейнера эта операция может иметь как постоянную, так и линейную сложность. При использовании контейнеров, которые хранят элементы последовательно, таких как vector
deque
, iterator
может перейти на любое вычисленное значение за постоянное время. Но при использовании контейнера на основе узлов, такого как list
, такая операция произвольного доступа недоступна. Вместо этого приходится перебирать все элементы, пока не будет найден нужный. Это очень дорого. Именно поэтому выбор контейнера, используемого в каждой конкретной ситуации, определяется требованиями к перебору элементов контейнера и их поиска в нем. (За более подробной информацией о работе стандартных контейнеров обратитесь к главе 6.)При использовании контейнеров, допускающих произвольный доступ, для доступа к элементам использования operator[]
iterator
. Это особенно важно при написании обобщенного алгоритма в виде шаблона функции, так как не все контейнеры поддерживают iterator
с произвольным доступом.С итератором можно делает еще много чего, но не с любым iterator
iterator
может принадлежать к одной из пяти категорий, обладающих разной степенью функциональности. Однако они не так просты, как иерархия классов, так что именно это я далее и опишу.Итераторы, предоставляемые различными типами контейнеров, не обязательно все умеют делать одно и то же. Например, vector
operator+=
, в то время как list::iterator
не позволяет. Разница между этими двумя типами итераторов определяется их Категории итераторов — это, по сути, интерфейс (не технически; для реализации категорий итераторов абстрактные базовые классы не используются). Имеется пять категорий, и каждая предлагает увеличение возможностей. Вот как они выглядят — от наименее до наиболее функциональной.
Итератор ввода поддерживает переход вперед с помощью p++
++p
и разыменовывание с помощью *p
. При его разыменовывании возвращается rvalue
, iterator
ввода используется для таких вещей, как потоки, где разыменовывание итератора ввода означает извлечение очередного элемента из потока, что позволяет прочесть только один конкретный элемент.