p != &arrStr[ARRAY_SIZE]; ++p) {
cout << *p << endl;
}
// Использование стандартных алгоритмов со стандартной последовательностью
list
unique_copy(&arrStr[0], &arrStr[ARRAY_SIZE],
back_inserter(lstStrDest));
}
Итератор — это тип, который используется для ссылки на единственный объект в контейнере. Стандартные контейнеры используют итераторы как основной механизм для доступа к содержащимся в них элементам. Итератор ведет себя как указатель; для доступа к объекту, на который указывает итератор, вы его разыменовываете (с помощью операторов *
->
), а для перевода итератора вперед или назад используется синтаксис, аналогичный арифметике указателей. Однако есть несколько причин, по которым итератор — это не то же самое, что указатель. Однако перед тем, как я покажу их, давайте рассмотрим основы использования итераторов.Итератор объявляется с помощью типа, элементы которого с его помощью будут перебираться. Например, в примере 7.1 используется list
list
Если вы не работали со стандартными контейнерами, то часть этого объявления ::iterator
list typedef
, предназначенный именно для этой цели — чтобы пользователи контейнера могли создать итератор для данного конкретного экземпляра шаблона. Это стандартное соглашение, которому следуют все стандартные контейнеры. Например, можно объявить итератор для list
или для set
, как здесь.list
set
Возвращаясь обратно к нашему примеру, итератор о инициализируется первым элементом последовательности, который возвращается методом begin
operator++
. Можно использовать как префиксный инкремент так и постфиксный инкремент (p++
), аналогично указателям на элементы массивов, но префиксный инкремент не создает временного значения, так что он более эффективен и является предпочтительным. Постфиксный инкремент (p++
) должен создавать временную переменную, так как он возвращает значение p
до его инкрементирования. Однако он не может инкрементировать значение после того, как вернет его, так что он вынужден делать копию текущего значения, инкрементировать текущее значение, а затем возвращать временное значение. Создание таких временных переменных с течением времени требует все больших и больших затрат, так что если вам не требуется именно постфиксное поведение, используйте префиксный инкремент.Как только будет достигнут элемент end
end
. В отношении стандартных контейнеров принято некое мистическое значение, которое представляет элемент, идущий сразу за последним элементом последовательности, и именно оно возвращается методом end
. Этот подход работает в цикле for
, как в этом примере:for (list
p != lstStr.end(); ++p) {
cout << *p << endl;
}
Как только p
end
, p
больше не может увеличиваться. Если контейнер пуст, то begin == end
равно true
, и тело цикла никогда не выполнится. (Однако для проверки пустоты контейнера следует использовать метод empty
, а не сравнивать begin
и end
или использовать выражение вида size == 0
.)Это простое объяснение функциональности итераторов, но это не все. Во-первых, как только что было сказано, итератор работает как rvalue
lvalue
, что означает, что его разыменованное значение можно присваивать другим переменным, а можно присвоить новое значение ему. Для того чтобы заменить все элементы в списке строк, можно написать нечто подобное следующемуfor (list
p != lstStr.end(); ++p) {
*p = "mustard";
}
Так как *p
string
, для присвоения элементу контейнера новой строки используется выражение string::operator=(const char*)
. Но что, если lstStr
— это объект типа const
? В этом случае iterator
не работает, так как его разыменовывание дает не-const объект. Здесь требуется использовать const_iterator
, который возвращает только rvalue
. Представьте, что вы решили написать простую функцию для печати содержимого контейнера. Естественно, что передавать контейнер следует как const
-ссылку.template
void printElements(const T& cont) {
for(T::const_iterator p = cont.begin();