Предыдущая Оглавление Следующая

Стандартная библиотека шаблонов (STL).

STL обеспечивает общецелевые, стандартные классы и функции, которые реализуют наиболее популярные и широко используемые алгоритмы и структуры данных.

STL строится на основе шаблонов классов, и поэтому входящие в нее алгоритмы и структуры применимы почти ко всем типам данных.

Ядро библиотеки образуют три элемента: контейнеры, алгоритмы и итераторы.

Контейнеры (containers) - это объекты, предназначенные для хранения других элементов. Например, вектор, линейный список, множество.

Ассоциативные контейнеры (associative containers) позволяют с помощью ключей получить быстрый доступ к хранящимся в них значениям.

В каждом классе-контейнере определен набор функций для работы с ними. Например, список содержит функции для вставки, удаления и слияния элементов.

Алгоритмы (algorithms) выполняют операции над содержимым контейнера. Существуют алгоритмы для инициализации, сортировки, поиска, замены содержимого контейнеров. Многие алгоритмы предназначены для работы с последовательностью (sequence), которая представляет собой линейный список элементов внутри контейнера.

Итераторы (iterators) - это объекты, которые по отношению к контейнеру играют роль указателей. Они позволяют получить доступ к содержимому контейнера примерно так же, как указатели используются для доступа к элементам массива.

С итераторами можно работать так же, как с указателями. К ним можно применить операции *, инкремента, декремента. Типом итератора объявляется тип iterator, который определен в различных контейнерах.

Существует пять типов итераторов:

  1. Итераторы ввода (input iterator) поддерживают операции равенства, разыменования и инкремента.
    ==, !=, *i, ++i, i++, *i++
    Специальным случаем итератора ввода является istream_iterator.
  2. Итераторы вывода (output iterator) поддерживают операции разыменования, допустимые только с левой стороны присваивания, и инкремента.
    ++i, i++, *i = t, *i++ = t
    Специальным случаем итератора вывода является ostream_iterator.
  3. Однонаправленные итераторы (forward iterator) поддерживают все операции итераторов ввода/вывода и, кроме того, позволяют без ограничения применять присваивание.
    ==, !=, =, *i, ++i, i++, *i++
  4. Двунаправленные итераторы (bidirectional iterator) обладают всеми свойствами forward-итераторов, а также имеют дополнительную операцию декремента (--i, i--, *i--), что позволяет им проходить контейнер в обоих направлениях.
  5. Итераторы произвольного доступа (random access iterator) обладают всеми свойствами bidirectional-итераторов, а также поддерживают операции сравнения и адресной арифметики, то есть непосредственный доступ по индексу.
    i += n, i + n, i -= n, i - n, i1 - i2, i[n], i1 < i2, i1 <= i2, i1 > i2, i1 >= i2

В STL также поддерживаются обратные итераторы (reverse iterators). Обратными итераторами могут быть либо двунаправленные итераторы, либо итераторы произвольного доступа, но проходящие последовательность в обратном направлении.

Вдобавок к контейнерам, алгоритмам и итераторам в STL поддерживается еще несколько стандартных компонентов. Главными среди них являются распределители памяти, предикаты и функции сравнения.

У каждого контейнера имеется определенный для него распределитель памяти (allocator), который управляет процессом выделения памяти для контейнера.

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

В некоторых алгоритмах и контейнерах используется функция особого типа, называемая предикатом. Предикат может быть унарным и бинарным. Возвращаемое значение: истина либо ложь. Точные условия получения того или иного значения определяются программистом. Тип унарных предикатов UnPred, бинарных - BinPred . Тип аргументов соответствует типу хранящихся в контейнере объектов.

Определен специальный тип бинарного предиката для сравнения двух элементов. Он называется функцией сравнения (comparison function). Функция возвращает истину, если первый элемент меньше второго. Типом функции является тип Comp .

Особую роль в STL играют объекты-функции.

Объекты-функции - это экземпляры класса, в котором определена операция "круглые скобки" (). В ряде случаев удобно заменить функцию на объект-функцию. Когда объект-функция используется в качестве функции, то для ее вызова используется operator().

class less
{
public:
  bool operator()(int x, int y)
  {
    return x < y; 
  }
};

В STL определены два типа контейнеров: последовательности и ассоциативные.

Ключевая идея для стандартных контейнеров заключается в том, что когда это представляется разумным, они должны быть логически взаимозаменяемыми. Пользователь может выбирать между ними, основываясь на соображениях эффективности и потребности в специализированных операциях. Например, если часто требуется поиск по ключу, можно воспользоваться map (ассоциативным массивом). С другой стороны, если преобладают операции, характерные для списков, можно воспользоваться контейнером list. Если добавление и удаление элементов часто производится в концы контейнера, следует подумать об использовании очереди queue, очереди с двумя концами deque, стека stack. По умолчанию пользователь должен использовать vector ; он реализован, чтобы хорошо работать для самого широкого диапазона задач.

Идея обращения с различными видами контейнеров и, в общем случае, со всеми видами источников информации - унифицированным способом ведет к понятию обобщенного программирования. Для поддержки этой идеи STL содержит множество обобщенных алгоритмов. Такие алгоритмы избавляют программиста от необходимости знать подробности отдельных контейнеров.

В STL определены следующие классы-контейнеры (в угловых скобках указаны заголовочные файлы, где определены эти классы):

Типы:

Итераторы:

Доступ к элементам:

Включение элементов:

Удаление элементов

Другие операции

Операции присваивания

Ассоциативные операции

Вектор vector в STL определен как динамический массив с доступом к его элементам по индексу.

template <class T, class Allocator = allocator<T> > class std::vector
{
 // ...
};

где T - тип предназначенных для хранения данных. Allocator задает распределитель памяти, который по умолчанию является стандартным.

В классе vector определены следующие конструкторы:

explicit vector(const Allocator &a = Allocator());

explicit vector(size_type число, const T &значение = T(),
                const Allocator &a = Allocator());

vector(const vector<T, Allocator> &объект);

template<class InIter> vector(InIter начало, InIter конец,
                              const Allocator &a = Allocator());

Первая форма представляет собой конструктор пустого вектора. Во второй форме конструктора вектора число элементов - это число, а каждый элемент равен значению значение. Параметр значение может быть значением по умолчанию. Третья форма конструктора вектор - это конструктор копирования. Четвертая форма - это конструктор вектора, содержащего диапазон элементов, заданный итераторами начало и конец.

vector<int> a;
vector<double> x(5);
vector<char> c(5, '*');
vector<int> b(a); // b = a

Для любого объекта, который будет храниться в векторе, должен быть определен конструктор по умолчанию. Кроме того, для объекта должны быть определены операторы < и == .

Для класса вектор определены следующие операторы сравнения:

==, <, <=, !=, >, >=.

Кроме этого, для класса vector определяется оператор индекса [].

#include <iostream.h>
#include <vector.h>

using namespace std;

int main(void)
{
  vector<int> v;
  for(int i = 0; i < 10; i++)
  {
    v.push_back(i);
  }
  cout << "size = " << v.size() << "\n";
  for (int i = 0; i < 10; i++)
  {
    cout << v[i] << " ";
  }
  cout << endl;
  for (int i = 0; i < 10; i++)
  {
    v[i] = v[i] + v[i];
  }
  for (int i = 0; i < v.size(); i++)
  {
    cout << v[i] << " ";
  }
  cout << endl;
  return 0;
}

 Доступ к вектору через итератор

#include <iostream.h>
#include <vector.h>

using namespace std;

int main(void)
{
  vector<int> v;
  for (int i = 0; i < 10; i++)
  {
    v.push_back(i);
  }
  cout << "size = " << v.size() << "\n";
  vector<int>::iterator p = v.begin();
  while (p != v.end())
  {
    cout << *p << " ";
    p++;
  }
  return 0;
}

 Вставка и удаление элементов

#include <iostream.h>
#include <vector.h>

using namespace std;

int main(void)
{
  vector<int> v(5, 1);
  // вывод
  for (int i = 0; i < 5; i++)
  {
    cout << v[i] << " ";
  }
  cout << endl;
  vector<int>::iterator p = v.begin();
  p += 2;
  // вставить 10 элементов со значением 9
  v.insert(p, 10, 9);
  //вывод
  p = v.begin();
  while (p != v.end())
  {
    cout << *p << " ";
    p++;
  }
  // удалить вставленные элементы
  p = v.begin();
  p += 2;
  v.erase (p, p + 10);
  // вывод
  p = v.begin();
  while (p != v.end())
  {
    cout << *p << " ";
    p++;
  }
  return 0;
}

 Вектор содержит объекты пользовательского класса

#include <iostream.h>
#include <vector.h>
#include "student.h"

using namespace std;

int main(void)
{
  vector<STUDENT> v(3);
  v[0] = STUDENT("Иванов", 45.9);
  v[1] = STUDENT("Петров", 30.4);
  v[0] = STUDENT("Сидоров", 55.6);
  // вывод
  for (int i = 0; i < 3; i++)
  {
    cout << v[i] << " ";
  }
  cout << endl;
  return 0;
}

Ассоциативный массив содержит пары значений. Зная одно значение, называемое ключом (key), мы можем получить доступ к другому, называемому отображенным значением (mapped value).

Ассоциативный массив можно представить как массив, для которого индекс не обязательно должен иметь целочисленный тип:

V &operator [](const K &) возвращает ссылку на V, соответствующий K .

Ассоциативные контейнеры - это обобщение понятия ассоциативного массива.

Ассоциативный контейнер map - это последовательность пар (ключ, значение), которая обеспечивает быстрое получение значения по ключу. Контейнер map предоставляет двунаправленные итераторы.

Ассоциативный контейнер map требует, чтобы для типов ключа существовала операция < . Он хранит свои элементы отсортированными по ключу так, что перебор происходит по порядку.

Спецификация шаблона для класса map:

template<class Key, class T, class Comp = less<Key>,
         class Allocator = allocator<pair> >
class std::map;

В классе map определены следующие конструкторы:

explicit map(const Comp &c = Comp(), const Allocator &a = Allocator());

map(const map<Key, T, Comp, Allocator> &ob);

template<class InIter> map(InIter first, InIter last, const Comp &c = Comp(),
                           const Allocator &a = Allocator());

Первая форма представляет собой конструктор пустого ассоциативного контейнера, вторая - конструктор копии, третья - конструктор ассоциативного контейнера, содержащего диапазон элементов.

Определена операция присваивания:

map &operator =(const map &);

Определены следующие операции: ==, <, <=, !=, >, >= .

В map хранятся пары ключ/значение в виде объектов типа pair .

Создавать пары ключ/значение можно не только с помощью конструкторов класса pair, но и с помощью функции make_pair, которая создает объекты типа pair , используя типы данных в качестве параметров.

Типичная операция для ассоциативного контейнера - это ассоциативный поиск при помощи операции индексации ([]).

mapped_type &operator [](const key_type &K);

Множества set можно рассматривать как ассоциативные массивы, в которых значения не играют роли, так что мы отслеживаем только ключи.

template<classT, class Cmp = less<T>, class Allocator = allocator<T> >
class std::set 
{
  //...
};

Множество, как и ассоциативный массив, требует, чтобы для типа T существовала операция "меньше" (< ). Оно хранит свои элементы отсортированными, так что перебор происходит по порядку.

Каждый алгоритм выражается шаблоном функции или набором шаблонов функций. Таким образом, алгоритм может работать с очень разными контейнерами, содержащими значения разнообразных типов. Алгоритмы, которые возвращают итератор, как правило, для сообщения о неудаче используют конец входной последовательности. Алгоритмы не выполняют проверки диапазона на их входе и выходе. Когда алгоритм возвращает итератор, это будет итератор того же типа, что и был на входе. Алгоритмы в STL реализуют большинство распространенных универсальных операций с контейнерами, такие как просмотр, сортировка, поиск, вставка и удаление элементов.

Алгоритмы определены в заголовочном файле <algorithm.h> .

Ниже приведены имена некоторых наиболее часто используемых функций-алгоритмов STL.

  1. Немодифицирующие операции.
    for_earch() - выполняет операции для каждого элемента последовательности
    find() - находит первое вхождение значения в последовательность
    find_if() - находит первое соответствие предикату в последовательности
    count() - подсчитывает количество вхождений значения в последовательность
    count_if() - подсчитывает количество выполнений предиката в последовательности
    search() - находит первое вхождение последовательности как подпоследовательности
    search_n() - находит n-е вхождение значения в последовательность
  2. Модифицирующие операции.
    copy() - копирует последовательность, начиная с первого элемента
    swap() - меняет местами два элемента
    replace() - заменяет элементы с указанным значением
    replace_if() - заменяет элементы при выполнении предиката
    replace_copy() - копирует последовательность, заменяя элементы с указанным значением
    replace_copy_if() - копирует последовательность, заменяя элементы при выполнении предиката
    fill() - заменяет все элементы данным значением
    remove() - удаляет элементы с данным значением
    remove_if() - удаляет элементы при выполнении предиката
    remove_copy() - копирует последовательность, удаляя элементы с указанным значением
    remove_copy_if() - копирует последовательность, удаляя элементы при выполнении предиката
    reverse() - меняет порядок следования элементов на обратный
    random_shuffle() - перемещает элементы согласно случайному равномерному распределению ("тасует" последовательность)
    transform() - выполняет заданную операцию над каждым элементом последовательности
    unique() - удаляет равные соседние элементы
    unique_copy() - копирует последовательность, удаляя равные соседние элементы
  3. Сортировка.
    sort() - сортирует последовательность с хорошей средней эффективностью
    partial_sort() - сортирует часть последовательности
    stable_sort() - сортирует последовательность, сохраняя порядок следования равных элементов
    lower_bound() - находит первое вхождение значения в отсортированной последовательности
    upper_bound() - находит первый элемент, больший чем заданное значение
    binary_search() - определяет, есть ли данный элемент в отсортированной последовательности
    merge() - сливает две отсортированные последовательности
  4. Работа с множествами.
    includes() - проверка на вхождение
    set_union() - объединение множеств
    set_intersection() - пересечение множеств
    set_difference() - разность множеств
  5. Минимумы и максимумы.
    min() - меньшее из двух
    max() - большее из двух
    min_element() - наименьшее значение в последовательности
    max_element() - наибольшее значение в последовательности
  6. Перестановки.
    next_permutation() - следующая перестановка в лексикографическом порядке
    prev_permutation() - предыдущая перестановка в лексикографическом порядке
Предыдущая Оглавление Следующая