Перебор: итераторы и контейнеры
Контейнерные классы (container class) используются для хранения большого числа отдельных элементов. Типы stack и vect являются контейнерными классами. Многие операции с контейнерными классами предоставляют возможность удобного перебора (посещения) отдельных элементов. Кроме того, в С++ классы служат для достижения абстрактности. Абстракция – это игнорирование деталей, и класс игнорирует детали; он открыто предоставляет удобный способ для управления вычислительной задачей. В этой главе мы исследуем различные приемы для выполнения перебора и извлечения элементов класса. Один из способов состоит в создании итератора, функция которого – перебирать объекты в контейнерном классе. Итератор перемещается от элемента к элементу. Перебор послужит нам поводом для написания кода, использующего контейнерные классы, объекты – итераторы, а также (немного забегая вперед) алгоритмы из стандартной библиотеки шаблонов (STL).
Перебор
В традиционном программировании инструкция for используется как наиболее удачный способ для структурной итерации, особенно при обработке массивов.
// переработать все a[I] и что–нибудь сделать
for (i = 0; i < size; ++i )
sum += a[i];
Однородный агрегат a [ ] обрабатывается элемент за элементом. Инструкция for задает определенный порядок перебора и управляется индексом i , который «растет на глазах» Конкретные порядок перебора и индекс цикла являются обычно деталями реализации, не влияющими на вычисления. Вот еще один способ выполнить те же вычисления:
for (j=size – 1; j ! = 0; --j)
sum += a [ j ];
Отвлеченно вычисления можно задать так:
пока не исчерпаны элементы
sum += следующий элемент
Реализуем следующий элемент типом указателя, который подходит для выбора объектов в контейнере. Давайте заменим класс vect из раздела 7.7, «Перегрузка операторов присваивания и индексирования», на стр. 203, добавив в него объекты – итераторы. Забегая вперед, назовем новый класс vector – так же, как контейнер в стандартной библиотеке шаблонов.
В файле vector. H
class vector {
public:
typedef int* iterator; // указатель vector :: int
explicit vector (int n =1);
vector (const vector& v);
vector (const int a [ ], int n)
~vector ( ) {delete [ ] bp; }
iterator begin ( ) const {return bp; }
iterator end ( ) const {return bp + size; }
int ub ( ) const {return size –1; } // верхняя граница
int& operator [ ] (int i); // проверка границ
vector& operator = (const vector& v);
private:
iterator bp; // базовый указатель
int size; // число элементов
};
Функции begin ( ) и end ( ) возвращают границы заданного вектора.
Код для суммирования такого агрегата с использованием итератора будет выглядеть так:
vector v (n);
vector :: iterator p;
int sum = 0;
……..
for (p = v. begin ( ); p != v/ end ( ); ++p)
sum += *p;
Идиома перебора здесь использует тип указателя и две границы для обработки в стандартном цикле всех элементов контейнера.
Итераторы
Объекты – указатели и идиома итерационного прибора делают очень простым написание многих стандартных алгоритмов для контейнерных классов, таких как класс vector. Напишем несколько подобных элементарных алгоритмов.
В файле vector. h
ostream& operator<< (ostream& out, const vector& v)
{
vector :: iterator p;
for (p = v. begin ( ) ; p != v. end ( ) ; ++p)
out << *p << ‘\t’;
out << end1;
return out;
}
istream& operator<< (istream& in, vector& v)
{
vector :: iterator p;
for (p = v. begin ( ) ; p != v. end ( ) ; ++p)
in >> *p;
return in;
}
Здесь мы перегрузили два стандартных оператора ввода – вывода. В каждом случае перебор вполне понятен. Следуя эффективной, идиоматичной схеме, программист избегает обычных ловушек, таких как ошибка, связанная с выходом за границы массива.