- •Итераторы
- •Основные понятия
- •Классификация итераторов
- •Свойства итераторов различных типов
- •Последовательные итераторы
- •Двунаправленные итераторы2
- •Итераторы произвольного доступа3
- •Итераторы ввода
- •Итераторы вывода
- •Использование итераторов
- •Вставка одного или нескольких элементов в позицию, указываемую итератором
- •Адаптеры итераторов и итераторы потоков ввода-вывода
- •Итераторы потоков ввода-вывода
- •Итераторы вставки (insert iterators).
- •Функции advance и distance
- •Теги и свойства итераторов5
- •Как написать свой итератор
Итераторы
Итератор – это объект, используемый для представлением доступа к последовательности элементов. Например, это могут быть элементы какого-либо контейнера или поток данных от устройства или из файла.
Итераторы позволяют достичь большей взаимонезависимости частей библиотеки, так реализация алгоритма работающая с элементами контейнера через итераторы может быть использована для обработки любого контейнера поддерживающего итераторы для перечисления собственных элементов. Также это позволяет контейнеру ничего не знать об обрабатывающих его алгоритмах, единственное, что он должен обеспечить – доступ к своим элементам посредством итераторов.
Например, концепция итераторов позволяет использовать один и тот же алгоритм сортировки абсолютно без изменений для любого контейнера, допускающего изменение элементов: и для массива, и для списка, и даже, к примеру, для неупорядоченного бинарного дерева – за последовательный доступ к элементам будет отвечать итератор (только при этом надо понимать, что ОТСОРТИРОВАНО дерево будет с точки зрения последовательности, представляющей его элементы, какой это будет иметь смысл для самого дерева – на первый взгляд не очень понятно). Также можно заставить итератор работать как фильтр и перебирать не все элементы, а лишь удовлетворяющие некоторому условию (в STL фильтрующих итераторов нет, но такой итератор можно написать самостоятельно). Можно добавить и более сложную функциональность и заставить итератор, к примеру, изменять коллекцию – см. часть «Адаптеры итераторов», итераторы вставки.
Напомним, что перебираемые элементы вовсе не обязаны храниться в оперативной памяти – как уже говорилось, итератор перебирает элементы любой последовательности, которая вполне может оказаться, к примеру, потоком данных от устройства или последовательностью всех простых чисел.
Очевидно, реализация таких возможностей без помощи абстрагирования итераторов потребовала бы существенного вмешательства в код всех функций, использующих коллекцию или последовательность.
Основные понятия
Итераторы STL - это объекты, которые имеют operator*, возвращающий текущий элемент последовательности, этот элемент является объектом некоторого класса или встроенного типа T, называемого типом значения (value type) итератора. Для каждого типа итератора X, для которого определено равенство, имеется соответствующий знаковый целочисленный тип, называемый типом расстояния (distanсe type) итератора.
Для перемещения итератора на следующий элемент используется operator++(). В разных типах итераторов могут быть определены и другие операции: operator--() (для двунаправленного итератора), увеличение на произвольное число (итератор произвольного доступа) и т.п. – см часть «Классификация итераторов» и «Свойства итераторов различного типа».
operator*() и operator++() - это основные операции для всех типов итераторов STL.
Значения итератора, для которых operator* определён, называются разыменовываемыми (dereferenceable).
Существуют значения итераторов, указывающие за последний элемент коллекции – так называемые законечные (past-the-end) значения. Законечные значения не являются разыменовываемыми и используются для обозначения конца последовательности.
Итератор j называется доступным (reachable) из итератора i, если для итераторов данного типа определена операция равенства и существует конечная (возможно, нулевая) последовательность операций j++; j++; …. , после которой j==i. Если итераторы i и j относятся к разным коллекциям1, то ни один из них не доступен из другого. В противном случае если итераторы должны перебирать все элементы коллекции (или одинаковое их подмножество), то очевидно, что либо i доступен из j, либо j доступен из i.
Последовательность элементов задается парой итераторов [i,j), указывающих на начало и конец. При этом элемент, указываемый i, включается, а j – нет, т.е. последним рассматривается элемент, предыдущий j. Большинство алгоритмов библиотеки STL, а также методы классов оперируют с последовательностями такого вида. Например, класс vector содержит метод, который заполняет вектор содержимым, взятым из последовательности элементов заданных парой итераторов [first; last). Элемент, указываемый first включается, а указываемый last нет.
template <class T, class A=allocator<T>>
class vector
{
public:
// заполнение вектора элементами последовательности задаваемой
// итераторами [first:last)
template <class In>
void assign(In first, In last);
};
Последовательность [i,i) пуста. Последовательность [i,j) допустима, если и только если j доступен из i. Результат применения алгоритмов библиотеки к недопустимым последовательностям не определён.
Идея использования итератора может быть проиллюстрирована следующим примером (приведенный ниже пример не является кодом, использующим STL, это некий абстрактный код, цель которого продемонстрировать концепцию итераторов)
// В примере видно, что print_sequence не имеет никакой информации
// о той последовательности, которую обрабатывает, что позволяет
// обрабатывать с ее помощью любую последовательность представимую
// порой итераторов
typedef int *iterator;
void print_sequence(iterator first, iterator last)
{
while(first != last)
{
cout << *first << endl;
first++;
}
}
int array[100] = {/*...*/};
print_sequence(&array[0], &array[100]); // распечатать все
print_sequence(&array[10], &array[20]); // распечатать часть
Этот фрагмент иллюстрирует наиболее важные концепции, связанные с итераторами: функция, принимающая в качестве аргумента диапазон итераторов, проверка двух итераторов на равенство, разыменование итератора, продвижение итератора, использование в качестве итератора обычного указателя.
Классы и функции, связанные непосредственно с итераторами (без привязки к конкретному контейнеру), находятся в заголовочном файле <iterator>.
