
- •2.3. Итераторы
- •2.4. Функциональные объекты
- •2.5. Адаптеры
- •2.6. Аллокаторы
- •Глава 3. Отличие stl от других библиотек
- •3.1. Расширяемость
- •3.2. Взаимозаменяемость компонентов
- •3.3. Совместимость алгоритмов и контейнеров
- •Глава 4 Итераторы
- •4.1. Входные итераторы
- •4.2. Выходные итераторы
- •4.3. Однонаправленные итераторы
- •4.4. Двунаправленные итераторы
- •4.5. Итераторы с произвольным доступом
2.3. Итераторы
Понимание итераторов является ключом к пониманию STL и тому, как наилучшим
образом работать с этой библиотекой. Обобщенные алгоритмы STL написаны с применением в качестве параметров итераторов, а контейнеры STL редоставляют итераторы, которые затем могут "включаться" в алгоритмы, как это было показано на рис. 1.1. На рис. 2.1 вновь показано это взаимоотношение, а также отношения между другими важными категориями компонентов STL. Эти очень обобщенные компоненты спроектированы для "подключения" друг к другу несметным количеством разных способов для получения больших и более
специализированных компонентов, которые можно найти в других библиотеках. Основным типом "проводов" для соединения компонентов является категория, именуемая итераторами (на рис. 1.1 и 2.1 эта категория схематически показана в виде соединительных шлейфов, а иерархия самих итераторов показана на рис. 2.2). Одним из видов итераторов является обычный указатель C++, но могут быть и иные итераторы, отличные от указателей. Такие другие типы итераторов, однако, должны вести себя как указатели в том смысле, что должны поддерживать операции наподобие ++ и *, причем ожидается, что эти операции ведут себя так же, как и с указателями: например, ++i перемещает итератор i к следующему положению, a *i возвращает местоположение, где может быть сохранено значение (т.е. можно записать * i = x), или значение из которого может быть использовано в выражении (как в х = * i).
Рис. 2.1. Пять из шести основных категорий компонентов STL (не показаны
аллокаторы)
Рассмотрим обобщенный алгоритм STL accumulate. При его вызове с итераторами
first и last и значением init, accumulate(first, last, init); добавляет к init значения в позициях от first до last (не включая значение в последней позиции) и возвращает получившуюся сумму. Например, мы можем написать следующую программу для вычисления и вывода суммы значений вектора.
Пример 2.11. Демонстрация обобщенной функции accumulate
#include <iostream>
#include <vector>
#include <cassert>
#include <numeric> // Алгоритм accumulate
using namespace std;
int main()
{
cout << "Демонстрация функции accumulate." << endl;
int х[5] = {2, 3, 5, 7, 11};
// Инициализация вектора элементами от х[0] до х[4]:
vector<int> vector1(&х[0], &х[5]);
int sum = accumulate(vector1.begin(), vector1.end(), 0);
assert (sum == 28);
cout << " Ok." << endl;
return 0;
}
Эта программа использует функцию accumulate для суммирования целых чисел из
контейнера vector1, который указывается с применением итераторов
vector1.begin () и vector1.end (). Можно воспользоваться функцией accumulate для работы непосредственно с массивом х, записав
sum = accumulate(&x[0], &х[5], 0);
или, например, со списком чисел с плавающей точкой double, как в следующем фрагменте:
double у[5] = {2.0, 3.0, 5.0, 7.0, 11.0};
list<double> list1(&у[0], &у[5]);
double sum = accumulate (list1.begin () , list1.end (), 0.0);
В каждом случае смысл действий один и тот же — прибавление к начальному значению значений из диапазона, указанного итераторами, однако типы итераторов и тип начального значения определяют, каким образом функция accumulate будет настроена для решения конкретной задачи.
Давайте рассмотрим, как функция accumulate использует итераторы. Она может быть определена следующим образом:
template <typename InputIterator, typename T>
T accumulate(InputIterator first, Inputlterator last, T init)
{
while (first != last)
{
init = init + *first;
++first;
}
return init;
}
Операции, выполняемые над итераторами, включают проверку неравенства итераторов с помощью оператора ! =, разыменование с применением оператора *, и инкремент с применением префиксного оператора ++. Эти операции, вместе с постфиксным оператором ++ и проверкой равенства ==, составляют все множество операций, поддержка которых требуется категорией входных итераторов. Другая характеристика входных итераторов (из-за которой они, собственно, и получили свое имя) — это то, что от операции разыменования *
требуется только возможность чтения из указанной позиции контейнера, но не записи в нее. В случае выходных операторов, наоборот, от операции разыменования * требуется только возможность записи в указанную позицию контейнера, но не чтения из нее; кроме того, поддержка проверок равенства и неравенства в этом случае не требуются.
STL определяет три другие категории итераторов — однонаправленные, двунаправленные итераторы и итераторы с произвольным доступом. За исключением входных и выходных итераторов, соотношения между итераторами образуют иерархию, показанную на рис. 2.2; т.е. каждая категория добавляет новые требования к требованиям, предъявляемым предыдущей категорией, что означает, что итераторы более поздней категории в иерархии одновременно являются членами более ранней. Например, двунаправленные итераторы
одновременно являются однонаправленными, а итераторы с произвольным доступом одновременно являются двунаправленными и однонаправленными.
Рис. 2.2. Иерархия категорий итераторов STL
Алгоритмы, такие как accumulate, find или merge, которые спроектированы для
работы со входными итераторами, являются более обобщенными, чем алгоритмы, требующие более мощных итераторов, — как, например, sort, который требует итераторов с произвольным доступом. Например, sort не может использоваться со списками STL, так как итераторы списков являются всего лишь двунаправленными, а не итераторами с произвольным доступом. Вместо этого STL предоставляет функцию-член, которая эффективно работает с двунаправленными итераторами. Как вы увидите в главе 4, "Итераторы", задача достичь высокой эффективности накладывает в STL ограничения на обобщенность некоторых алгоритмов, а организация итераторов в категории является основным средством достижения поставленной цели.