Шаг 6.
Библиотека шаблонов классов Borland.
Развитие "сознания" класса. Итераторы.

    На этом шаге мы рассмотрим альтернативный метод доступа к объектам контейнера.

    Итераторы предоставляют альтернативный метод доступа к объектам контейнера. Итераторами снабжаются абстрактные контейнеры вроде массивов и очередей, а также такие фундаментальные структуры, как списки и векторы.

   С помощью итератора вы можете "передвигаться" по объектам в структуре без их удаления. Например, обычно только верхний объект стека доступен для просмотра, для получения же других объектов необходимо удалять их, вызывая функцию Рор(). С помощью итератора можно "передвигаться" по стеку для выполнения операций над его содержимым.

    Простой пример демонстрирует, как использовать итераторы для просмотра объектов в очереди. Как и в программе DIRECT.CPP, в листинге 1 набор строк запоминается косвенным образом, но на этот раз в контейнере класса TIQueueAsDoubleList.

    Листинг 1. ITERATE.CPP (демонстрация итераторов)

#include <iostream.h>
#include <cstring.h>
#include <classlib\queues.h>

// Объявление глобального косвенного строкового контейнера очереди
TIQueueAsDoubleList iQueue;

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() значения индексов для того, чтобы начать итерационный процесс в ограниченном диапазоне объектов, хранящихся в контейнере-массиве.

    На следующем шаге мы рассмотрим принадлежность объектов.




Предыдущий шаг Содержание Следующий шаг