2. Основные элементы STL Ядро библиотеки STL образуют три основополагающих элемента: контейнеры, алгоритмы и итераторы. В программах на Си++ эти элементы обычно функционируют в тесной взаимосвязи.
Контейнеры (containers) – это объекты, предназначенные для хранения других объектов. Контейнеры бывают различных типов. Например, в классе vector определяется динамический массив, в классе queue – очередь, в классе list – связный список. В каждом классе-контейнере определен набор функций для работы с ним. Например, в списке есть функции для вставки и удаления элементов, для слияния двух списков. В стеке есть функции для помещения элемента в стек и извлечения элемента из стека.
Алгоритмы (algorithms) выполняют операции над содержимым контейнеров. Существуют алгоритмы для инициализации, сортировки, поиска или замены содержимого контейнеров. Многие алгоритмы предназначены для работы с последовательностью (sequence), которая представляет собой связный список элементов внутри контейнера.
Итераторы (iterators) – это объекты, которые по отношению к контейнерам играют роль указателей. Они позволяют получать доступ к содержимому контейнера примерно так же, как указатели используются для доступа к элементам массива.
В качестве примера рассмотрим применение алгоритма find для поиска первого вхождения заданного значения в контейнере. Итераторы стандартной библиотеки состоят из пар значений, отмечающих начало и конец структуры данных внутри контейнера. Алгоритм find использует пару итераторов и ищет первое вхождение заданного значения. Функция, реализующая алгоритм find, определена следующим образом: template
InputIterator find( InputIterator first, InputIterator last,
const T& value )
{
for ( ; first != last; ++first )
if ( *first == value )
break;
return first;
} Алгоритм будет работать с объектами любого класса, в том числе и с обычными массивами. В программе 9.1 демонстрируется поиск первого вхождения значения 7 в целочисленном массиве. #include
#include // У заголовочных файлов STL нет расширения // Обязательный оператор для подключения "пространства имен std"
// библиотеки STL к глобальному пространству имен программы
using namespace std; void main()
{
int data[100];
memset( data, 0, sizeof( data ) );
data[50] = 7;
int* where = find( data, data+100, 7 );
cout << *where << '\n';
}
Программа 9.1. Применение алгоритма find к целочисленному массиву. В программе 9.2 показано, как выполнить поиск первого вхождения в целочисленном списке. Вызов функции find() выглядит ненамного сложнее, чем в программе 9.1. #include
#include
#include using namespace std; void main()
{
list intList;
for ( int i = 0; i < 100; i++ )
intList.push_back( i ); list::iterator where;
where = find( intList.begin(), intList.end(), 7 );
cout << *where << '\n';
}
Программа 9.2. Применение алгоритма find к связному списку, в котором хранятся целые числа.
3. Итераторы В библиотеке STL служебные объекты-итераторы играют очень важную роль. Они обеспечивают пользователям и алгоритмам доступ к содержимому контейнеров. Можно считать, что итератор – это объект, выполняющий по отношению к контейнеру роль указателя, позволяющего организовать прохода по всем значениям, хранящимся в контейнере.
Как и обычные указатели, итераторы применяются для различных целей. Итератор может обозначать конкретное значение (как указатель указывает на конкретную переменную). С другой стороны, пара итераторов может задавать диапазон значений (два указателя отмечают границы непрерывного участка памяти). Однако в случае итераторов описываемые значения расположены друг за другом не физически, а логически. Это происходит, поскольку они взяты из одного контейнера и, следовательно, следуют друг за другом в том порядке, как они хранились в контейнере.
Обычные указатели иногда принимают значение NULL – то есть не указывают ни на какой объект. Итераторы аналогичным образом могут не определять какое-либо конкретное значение. Подобно тому, как логической ошибкой является разыменование и использование указателя со значением NULL, нельзя разыменовывать и применять итератор, который не определяет никакого значения.
Когда в языке Си++ используются два указателя, которые ограничивают область памяти, по соглашению второй указатель не рассматривается как часть области. Например, массив с именем x и длиной 10 иногда описывается как занимающий область от x до (x+10), хотя элемент (x+10) не является частью массива. На самом деле указатель (x+10) ссылается на запредельный элемент, то есть элемент, следующий за последним элементом описываемого диапазона. Итераторы определяют диапазон аналогичным образом. Второе значение рассматривается не как часть определяемого диапазона, а как запредельный элемент, описывающий значение, следующее в последовательности за последним значением из заданного диапазона.
Подобно традиционным указателям, основное действие, которое модифицирует итератор, это инкрементирование (оператор ++). Когда инкрементирование применяется к итератору, который указывает на последнее значение в последовательности, то итератор принимает описанное выше запредельное значение. Оператор разыменования (*) осуществляет доступ к данным, определяемым итератором.
Диапазон может описывать весь контейнер при задании итератора, который указывающего на первый элемент, и итератора, имеющего специальное "последнее" значение. Диапазоны могут описывать последовательности, входящие в контейнер (двум итераторам присваиваются конкретные значения). В стандартных контейнерах итератор начала контейнера возвращается функцией begin(), а итератор, обозначающий конец контейнера, – функцией end().
|