На этом шаге мы рассмотрим альтернативный метод доступа к объектам контейнера.
Итераторы предоставляют альтернативный метод доступа к объектам контейнера. Итераторами снабжаются абстрактные контейнеры вроде массивов и очередей, а также такие фундаментальные структуры, как списки и векторы.
С помощью итератора вы можете "передвигаться" по объектам в структуре без их удаления. Например, обычно только верхний объект стека доступен для просмотра, для получения же других объектов необходимо удалять их, вызывая функцию Рор(). С помощью итератора можно "передвигаться" по стеку для выполнения операций над его содержимым.
Простой пример демонстрирует, как использовать итераторы для просмотра объектов в очереди. Как и в программе DIRECT.CPP, в листинге 1 набор строк запоминается косвенным образом, но на этот раз в контейнере класса TIQueueAsDoubleList.
Листинг 1. ITERATE.CPP (демонстрация итераторов)
#include <iostream.h> #include <cstring.h> #include <classlib\queues.h> // Объявление глобального косвенного строкового контейнера очереди TIQueueAsDoubleListiQueue; int main() { // Запомнить несколько строк в очереди iQueue.Put(new string("Line up")); iQueue.Put(new string("for the")); iQueue.Put(new string("Magical")); iQueue.Put(new string("Tour")); // Объявить косвенный строковый итератор для iQueue TIQueueAsDoubleListIterator iterator(iQueue); // Применить итератор для сканирования по очереди cout << endl <<"Обход очереди с помощью итератора:" << endl; cout << "------------------------------------" << endl; while (iterator!= 0) { cout << *iterator.Current() << endl; iterator++; } // Применить стандартный метод извлечения объектов из очереди cout << endl << "Извлекаем объекты очереди:" << endl; cout << "------------------------------------" << endl; while (!iQueue.IsEmpty()) { string *p = iQueue.Get(); cout << *p << endl; delete p; } return 0; }
Результат работы программы:
Обход очереди с помощью итератора: ------------------------------------ Tour Mystery Magical for the Line up Извлекаем объекты очереди: ------------------------------------ Line up for the Magical Mystery Tour
В качестве контейнера в программе объявляется глобальный объект с именем iQueue. Поскольку контейнер - косвенный (его имя начинается с TI), в нем запоминаются указатели на строковые объекты:
TIQueueAsDoubleList<string> iQueue;
Как уже упоминалось, обычно лучше всего пользоваться оператором создания динамических объектов new для запоминания их в косвенном контейнере. Для добавления нескольких строк в очередь программа передает указатели, возвращаемые оператором new, функции-члену Put() всех контейнеров очередей. Функция Put() для очереди имеет такое же значение, как и функция Add() для массивов:
iQueue.Put(new string("Magical")); iQueue.Put(new string("Mystery"));
Затем в программе создается итератор класса, разработанный для совместного использования с классом TIQueueAsDoubleList. Для формирования имени класса добавьте в конце слово Iterator:
TIQueueAsDoubleListIteratоr<string> iterator(iQueue);
При использовании таких сложных имен полезно создать более короткий алиас с помощью оператора typedef, например:
typedef TIQueueAsDoubleListIterator<string> TIterator;
Директива typedef означает, что отныне TIterator - синоним предыдущего словосочетания. Объявление контейнера теперь выглядит намного легче для чтения:
TIterator iterator(iQueue);
В любом случае, объявление создаст итератор (который мы назвали iterator) типа TIQueueAsDoubleListIterator<string>. Передача объекта iQueue конструктору итератора связывает итератор с этим контейнером. Операции с использованием итератора будут выполняться над данными контейнера iQueue.
Например, выражение iterator.Current() возвращает текущий элемент контейнера Queue - первого объекта при первом обращении. Если контейнер пуст, итератор вернет нулевой указатель (или нулевую ссылку, если контейнер запоминает сами объекты).
При использовании итератора в выражениях имейте в виду, что он возвращает целое значение. Если это значение равно нулю (ложь), значит итератор завершил сканирование данных в контейнере.
С помощью функций и значений итератора можно перемещаться по очереди, отображая объекты контейнера в том порядке, в котором они запоминались:
while (iterator != 0) //Использовать перегруженный оператор int() { cout << *iterator.Сиrrent() << endl; // Извлечь текущий объект iterator++; // Переместить итератор к следующему объекту }
Во всех итераторах реализован оператор инкремента ++ (как постфиксный, так и префиксный). Как продемонстрировано в предыдущем фрагменте, вы можете пользоваться выражениями iterator++ (или ++iterator) для перемещения объекта итератора к следующему объекту в контейнере. Оператор ++ возвращает объект того же типа, что и контейнер, - этим фактом вы можете воспользоваться для эффективной реализации циклов. Установим итератор в начальное положение с помощью обращения к его функции Restart() и объявим указатель на строку р следующим образом:
iterator.Restart(); // Установить итератор на первый объект string *p; // Объявить указатель на строку
Теперь можно написать следующий цикл while (или что-нибудь в этом роде) для перемещения по контейнеру:
while (р = iterator++) // Извлечь текущий объект и продвинуть итератор cout << *р << endl; // Отобразить объект, адресуемый р
В управляющем выражении цикла while указателю р присваивается адрес текущего строкового объекта, кроме того итератор перемещается к следующему объекту. По достижении последнего объекта управляющее выражение принимает нулевое значение и цикл завершается. В простом операторе вывода в поток отображается каждый строковый объект.
Если вы скомпилируете предыдущий код, возможно, компилятор выдаст вам предупреждение "possibly incorrect assignment" ("возможно неверное присваивание"), поскольку оператор = в данном контексте распознается как возможная ошибка в написании оператора сравнения ==. Тем не менее, наличие единственного знака = в данном случае корректно, поскольку мы хотим присвоить значение, возвращаемое итератором, указателю р. Если вас беспокоит присутствие этого предупреждения, замените предыдущий код следующим фрагментом, он полностью эквивалентен приведенному выше, хотя и труднее читается:
while ((р = iterator++)!= 0)
cout << *p << endl;
Все итераторы имеют, по крайней мере, один конструктор, функцию Restart() и перегруженные операторы int() и ++. Некоторые итераторы снабжаются также дополнительными специальными функциями. Например, в итераторах массивов перегружается функция Restart с двумя параметрами:
void Restart(unsigned start, unsigned stop);
Можно передать функции Restart() значения индексов для того, чтобы начать итерационный процесс в ограниченном диапазоне объектов, хранящихся в контейнере-массиве.
На следующем шаге мы рассмотрим принадлежность объектов.