Лекции 13 - 15 Библиотека STL языка С++: контейнеры, итераторы, функторы, алгоритмы
Библиотека STL В стандарте языка С++ присутствует библиотека STL (Standard Type Library) в которой реализован набор обобщенных классов для работы с произвольными типами данных. Главными составляющими этой библиотеки являются: n Контейнеры – коллекция однотипных значений, n Итераторы – обеспечивают последовательный доступ к элементам контейнера. n Алгоритмы – обобщенные функции, реализующие алгоритмы обработки элементов контейнера. Основная концепция STL – разделение данных и операций. Данные хранятся в контейнерах, а операции определяются адаптируемыми алгоритмами. Итераторы «склеивают» эти два компонента.
Схема применения компонентов STL Контейнер Итератор Алгоритм Контейнер Итерато р Контейнер
Контейнеры Контейнерные классы (контейнеры) – управляют коллекциями (набором) однотипных элементов. Контейнеры подразделяются на: n n последовательные контейнеры – представляют собой упорядоченные коллекции, в которых каждый элемент занимает определенную позицию. Позиция зависит от времени и места вставки, но не связана со значением элемента. ассоциативные контейнеры – представляют собой отсортированные коллекции, в которых позиция элемента зависит от его значения по определенному критерию сортировки.
Последовательные контейнеры Виды последовательных контейнеров: n n n список – list (библиотека <list>), вектор – vector (библиотека <vector>), стек – stack (библиотека <stack>), очередь – queue (библиотека <queue>), дек – deque (библиотека <deque>), приоритетная очередь – priority_queue (библиотека <queue>).
Ассоциативные контейнеры Виды ассоциативных контейнеров: n множество – set (библиотека <set>); n мультимножество – multiset (библиотека <set>); n отображение – map (библиотека <map>); n мультиотображение – map (библиотека <map>). Дополнительные контейнеры: n vector<bool> - булевский вектор; n bitset – битовые поля.
Требования к элементам контейнеров Элементы STL-контейнера могут быть произвольного типа, но этот тип должен удовлетворять определенным требованиям: n n n для типа должен быть определен конструктор копирования; для типа должна быть определена операция присваивания; деструктор должен быть описан в разделе public.
Исключения Классы библиотеки STL могут генерировать два вида исключений: n out_of_range – при выходе индекса за пределы контейнера; n bad_alloc – при нехватке памяти. Те не менее, стандарт языка С++ не гарантирует полной проверки всех возможных ошибок, поэтому это необходимо выполнять программисту.
Общие операции над контейнерами Cont. Type c; Создает пустой контейнер, не содержащий элементов Cont. Type c(c 1); Создает копию контейнера того же типа Cont. Type c(beg, end); Создает контейнер и инициализирует копиями всех элементов в интервале [beg, end) c. ~Cont. Type() Удаляет все элементы и освобождает память c. size() Возвращает фактическое количество элементов c. empty() Проверяет, пуст ли контейнер (0 – нет, !0 – пуст) c. max_size() Возвращает максимально возможное количество элементов c 1 == c 2 Проверяет на равенство c 1 и c 2 c 1 != c 2 Проверяет на неравенство c 1 и c 2
Общие операции над контейнерами c 1 < c 2 Проверяет, что с1 меньше с2 c 1 > c 2 Проверяет, что с1 больше с2 c 1 <= c 2 Проверяет, что с1 не больше с2 c 1 >= c 2 Проверяет, что с1 не меньше с2 c 1 = c 2 Присваивает с1 все элементы с2 c 1. swap(c 2) Меняет местами содержимое с1 и с2 swap(c 1, c 2) Меняет местами содержимое с1 и с2
Общие операции над контейнерами c. begin() Возвращает итератор для первого элемента c. end() Возвращает итератор для позиции за последним элементом c. rbegin() Возвращает обратный итератор для первого элемента при переборе в обратном направлении c. rend() Возвращает обратный итератор для позиции за последним элементом при переборе в обратном направлении c. insert(pos, elem) Вставляет копию elem c. erase(beg, end) Удаляет все элементы из интервала [beg, end) c. clear() Удаляет из контейнера все элементы c. get_allocator() Возвращает модель памяти контейнера
Вектором называется абстрактная модель, имитирующая динамический массив при операциях с элементами. #include <vector> using namespace std; int main(int argc, char *argv[]) { … vector<int> vec; … }
Возможности вектора Элементы вектора копируются во внутренний динамический массив. Вектор обеспечивает произвольный доступ к своим элементам, поэтому обращение к любому элементу с известной позицией выполняется напрямую и с постоянным временем. Итераторы вектора являются итераторами произвольного доступа, что позволяет применять к векторам все алгоритмы STL. Операции присоединения и удаления элементов в конце вектора выполняются с высоким быстродействием. В тоже время операции вставки и удаления элементов внутри массива выполняются медленнее. Дополнительно вектор поддерживает операции контроля и изменения своих размеров.
Операции вектора Операции создания, копирования и уничтожения vector<Elem> c; Создает пустой вектор, не содержащий ни одного элемента vector<Elem> c 1(c 2); Создает копию другого вектора того же типа (с копированием всех элементов) vector<Elem> c(n); Создает вектор из n элементов, создаваемых конструктором по умолчанию vector<Elem> c(n, elem); Создает вектор, инициализируемый n копиями элемента elem vector<Elem> c(beg, end); Создает вектор, инициализируемый значениями из интервала [beg, cur) c. ~vector<Elem>() Уничтожает все элементы и освобождается память
Операции вектора Не модифицирующие операции над векторами capacity(); Возвращает максимально возможное количество элементов без перераспределения памяти reserve(n); Увеличивает емкость вектора, если текущая емкость меньше заданной Операции присваивания c. assign(n, elem) Присваивает n копий заданного элемента c. assign(beg, end) Присваивает элементы интервала [beg, end) Обращение к элементам c. at(ind) Возвращает элемент с индексом ind (с проверкой) c[ind] Возвращает элемент с индексом ind (без проверки) c. front(); Возвращает первый элемент (без проверки) c. back(); Возвращает последний элемент (без проверки)
Операции вектора Вставка и удаление элементов c. insert(pos, elem); Вставляет в позицию итератора pos копию элемента elem и возвращает позицию нового элемента c. insert(pos, n, elem); Вставляет в позицию итератора pos n копий элемента elem (не возвращает значений) c. insert(pos, beg, end); Вставляет копию всех элементов интервала [beg, end) в позицию итератора pos (не возвращает значений) c. push_back(elem); Присоединяет копию elem в конец вектора c. pop_back(); Удаляет последний элемент, не возвращая его c. erase(pos) Удаляет элемент в позиции итератора pos и возвращает позицию следующего элемента c. erase(beg, end) Удаляет все элементы из интервала [beg, end) и возвращает позицию следующего элемента c. resize(num) Приводит контейнер к размеру num. Новые элементы, если они есть, инициализируются конструктором по умолчанию c. resize(num, elem) Приводит контейнер к размеру num. Новые элементы, если они есть, инициализируются значением elem
Обработка исключений Если функции, вызванные вектором, инициируют исключения, то библиотека STL гарантирует следующее: n если исключение происходит при вставке элемента функцией push_back, эта функция не вносит изменений в контейнер; n если операция копирования не генерирует исключений, то функция insert() либо выполняется успешно, либо не вносит изменений; n функция pop_back не генерирует исключений; n если операция копирования не генерирует исключений, то функции erase() и clear() тоже не генерируют исключений; n функция swap() не генерирует исключений; n если используемые элементы не генерируют исключений во время операций копирования, то любая операция либо выполняется успешно, либо не вносит изменений в контейнер.
Класс vector<bool> Этот класс специально определен в STL для организации массива логических значений. Каждое значение в этом массива занимает объем равный 1 биту. Специальные операции класса c. flip(); Инвертирует все логические элементы m[ind]. flip(); Инвертирует логический элемент с заданным индексом m[ind]=val; Присваивает значение логическому элементу с индексом ind (копирование одного бита) m[ind 1] = m[ind 2]; Присваивает значению логического элемента с индексом ind 1 значение логического элемента с индексом ind 2
Дек – двунаправленная очередь – структура оптимизированная для добавления и удаления элементов с обоих концов. #include<deque> using namespace std; int main(int argc, char *argv[]) { … deque<double> deq; … }
Возможности дека Основные отличия от векторов: n n n Вставка и удаление выполняются быстро как в начале, так и в конце структуры. Внутренняя структура содержит дополнительный уровень ссылок, поэтому обращение к элементам и перемещение итератора в деках обычно выполняются медленнее чем в векторах. В системах с ограниченными размерами блоков памяти дек может содержать больше элементов, поскольку он не ограничивается одним блоком памяти. Деки не позволяют управлять емкостью и моментами перераспределения памяти. Освобождение не используемых блоков может привести к уменьшению объема памяти, занимаемой деком.
Возможности дека Особенности характерные как для деков, так и для векторов: n вставка и удаление элементов внутри контейнеров выполняется относительно медленно. n итераторы являются итераторами произвольного доступа. Выбор дека целесообразен, если выполняются следующие условия: n вставка элементов выполняется с обоих концов; n в программе не используются ссылки на элементы контейнера; n важно, чтобы контейнер освобождал неиспользуемую память.
Операции над деком Конструкторы и деструкторы deque<Elem> c; Создает пустой дек, не содержащий ни одного элемента deque<Elem> c 1(c 2); Создает копию другого дек того же типа (с копированием всех элементов) deque<Elem> c(n); Создает дек из n элементов, создаваемых конструктором по умолчанию deque<Elem> c(n, elem); Создает дек, инициализируемый n копиями элемента elem deque<Elem> c(beg, end); Создает дек, инициализируемый значениями из интервала [beg, cur) c. ~deque<Elem>() Уничтожает все элементы и освобождает память
Операции над деком Операции деков отличаются от операций векторов только в следующих отношениях: n деки не поддерживают функции связанные с емкостью (capacity() и reserve()); n в деках определены прямые функции вставки и удаления первого элемента (push_front(elem) и pop_front()). При работе с деками необходимо учитывать следующее: n функции обращения к элементам (кроме at()) не проверяют правильность индексов и итераторов; n вставка или удаление элементов может привести к перераспределению памяти, что сделает недействительными все указатели или ссылки на элементы дека.
Обработка исключений В целом обработка исключений в деках аналогична обработке в векторах. Библиотека С++ обеспечивает следующие гарантии: n n если при вставке элемента функцией push_back() или push_front() происходит исключение, то эти функции не вносят изменений; функции pop_back() и pop_front() не генерируют исключений.
Список – контейнер реализованный на базе двунаправленного связанного списка. #include<list> using namespace std; int main(int argc, char *argv[]) { … list<double> lst; … }
Возможности списка Основные отличия списка от вектора и дека: n n список не поддерживает произвольный доступ к элементам; вставка и удаление элементов выполняется быстро, независимо от расположения элемента; в результате вставки и удаления элементов указатели, ссылки и итераторы, относящиеся к другим элементам, остаются действительными; обработка исключений в списках реализована так, что любая операция завершается успешно, или не вносит изменений.
Возможности списка n Поскольку списки не поддерживают произвольный доступ к элементам, в них не определены ни оператор индексирования, ни функция at(). n Списки не поддерживают операции связанные с емкостью и перераспределением памяти, потому что такие операции просто не нужны. n Списки поддерживают много специальных функций для перемещения элементов.
Операции над списками Конструкторы и деструкторы list<Elem> c; Создает пустой список, не содержащий ни одного элемента list<Elem> c 1(c 2); Создает копию другого списка того же типа (с копированием всех элементов) list<Elem> c(n); Создает список из n элементов, создаваемых конструктором по умолчанию list<Elem> c(n, elem); Создает список, инициализируемый n копиями элемента elem list<Elem> c(beg, end); Создает список, инициализируемый значениями из интервала [beg, cur) c. ~list<Elem>() Уничтожает все элементы и освобождает память
Операции над списками Для списка определены все стандартные для контейнеров операции. Кроме того, список поддерживает все операции для помещения и изъятия элементов из конца или начала списка, удаления и вставки элементов. Кроме того в списке реализованы следующие операции: c. front() Возвращает первый элемент (без проверки на существование) c. back() Возвращает последний элемент (без проверки на существование) c. remove(val) Удаление всех элементов со значением val c. remove_if(op) Удаление всех элементов, для которых op(elem) вернет true
Операции над списками Функции врезки c. unique() Удаляет дубликаты c. unique(op) Удаляет дубликаты для которых op возвращает true c 1. splice(pos, c 2) Перемещает все элементы c 2 в с1 перед позицией итератора pos c 1. splice(pos, c 2, pos 2) Перемещает все элементы с2, начиная с позиции итератора pos 2, в с1 перед позицией итератора pos c 1. splice(pos, c 2, beg, end) Перемещает все элементы интервала [beg, end) с2 перед позицией итератора pos списка с1 c. sort() Сортирует все элементы оператором < c. sort(op) Сортирует все элементы по критерию op c 1. merge(c 2); Перемещает все элементы из с2 в с1 с сохранением сортировки c 1. merge(c 2, op); Перемещает все элементы из с2 в с1 с сохранением сортировки по op c. reverse() Переставляет все элементы в обратном порядке
Обработка исключений Из всех стандартных контейнеров STL списки наиболее надежны. Практически все операции над списками либо завершаются успешно, либо не вносят изменений. Такая гарантия не предоставляется только оператором присваивания и функцией sort(). Функции merge(), remove_if() и unique() предоставляют гарантии при условии, что исключения не будут сгенерированы при сравнении элементов оператором == или предикатом.
Стек Класс stack реализует стек, функционирующий по принципу LIFO. Метод push помещает значение в стек, а метод pop удаляет элементы в порядке обратному их вставке. Описание класса стек: template<class T, class Container=deque<T>> class stack; В качестве контейнера для организации стека может использоваться любой последовательный контейнер поддерживающий методы back(), push_back(), pop_back(). #include<stack> using namespace std; int main(int argc, char *argv[]) { … stack<int, vector<int>> st; … }
Стек Основной интерфейс стеков состоит из методов: n push() – помещение элемента в стек; n top() – получение «верхнего» элемента стека; n pop() – удаление элемента из стека. Перед вызовом методов top() и pop() необходимо убедиться, что в стеке есть элементы.
Очередь Класс queue реализует очередь, работающую по принципу FIFO. Метод push() заносит элементы в очередь, а метод pop() удаляет элементы из очереди в порядке их помещения. Описание класса очередь: template<class T, class Container=deque<T>> class queue; В качестве контейнера для организации стека может использоваться любой последовательный контейнер поддерживающий методы front(), back(), push_back(), pop_front(). #include<queue> using namespace std; int main(int argc, char *argv[]) { … stack<string, list<string>> st; … }
Очередь Основной интерфейс очередей состоит из следующих методов: n push() – помещение элемента в очередь; n front() – возвращает следующий элемент из очереди; n back() – возвращает последний элемент из очереди; n pop() – удаляет элемент из очереди. Если очередь не содержит ни одного элемента, выполнение функций front(), back() и pop() не определено.
Приоритетная очередь Класс priority_queue реализует очередь, в которой последовательность чтения элементов определяется их приоритетами. По своему интерфейсу приоритетные очереди близки к обычным очередям: n метод push() заносит новый элемент в очередь, n метод top() возвращает следующий элемент из очереди, n метод pop() удаляет элемент из очереди. Но в отличии от обычной очереди каждый раз изымается элемент с максимальным значением приоритета. template<class T, class Container=vector<T>, class Compare = less<typename Container: : value_type> > class priority_queue; Допускается использование любого последовательного контейнера с поддержкой методов произвольного доступа и методов front(), back(), push_back(), pop_front().
Приоритетная очередь #include<queue> using namespace std; int main(int argc, char *argv[]) { … priority_queue<float> buf 1; … priority_queue<int, deque<int>> buf 2; … priority_queue<double, vector<double>, greater<double> > buf 3; … }
Битовые поля моделируют массивы битов (логических величин) фиксированного размера. Основные достоинства класса bitset – произвольный размер битового поля и поддержка дополнительных операций (присваивание значений отдельных бит, чтение и запись битовых полей как последовательности нулей и единиц). Количество бит в битовом поле после его создания остается неизменным. Если необходимо реализовать битовое поле произвольной длины, то для этого нужно использовать класс vector<bool>. Описание класса содержится в библиотеке bitset: template<size_t bits> class bitset;
Битовые поля Конструкторы класса: n bitset<размер> bitset(); - конструктор по умолчанию; n bitset<размер> bitset(unsigned long); - конструктор с инициализацией целочисленным значением; n bitset<размер> bitset(const string &); - конструктор с инициализацией строкой; n bitset<размер> bitset(const string &, string: : size_type str_idx, string: : size_type str_num); конструктор с инициализацией строкой. Методы класса: n size() – размер поля (количество бит); n count() – возвращает количество установленных бит; n any() – проверяет наличие хотя бы одного бита, установленного в 1; n none() – проверяет отсутствие хотя бы одного бита, установленного в 1; n test(size_t idx) – проверяет установку бита с номером idx.
Битовые поля Модифицирующие методы битовых полей: n set() – устанавливает все биты; n set(size_t idx) – устанавливает бит с номером idx; n set(size_t idx, int value) – устанавливает бит с номером idx в значение value; n reset() – сбрасывает все биты; n reset(size_t idx) – сбрасывает бит с номером idx; n flip() – инвертирует состояние всех бит; n flip(size_t idx) – инвертирует состояние бита с номером idx; Для битовых полей определены операции: n |=, &=, ^=, >>=, <<= - преобразуют объект, n ~, &, |, ^, <<, >> - создают объект.
Битовые поля Операции преобразования типа: n to_ulong() – возвращает число типа usigned long содержащее младшие 32 бита битового поля; n to_string() – возвращает строку, содержащую 0 и 1 в соответствии с содержимым битового поля. Операции ввода-вывода: для битовых полей определены операции >> (ввод) и << (вывод)
Множества и мультимножества Мультимножество Множество 1 1 2 1 3 4 2 5 2 3
Множества и мультимножества Для использования множеств и мультимножеств необходимо подключить библиотеку <set> В ней описаны два класса: namespace std{ template<class T, class Compare=less<T>, class Allocator=allocator<T> > class set; template<class T, class Compare=less<T>, class Allocator=allocator<T> > class multiset; }
Множества и мультимножества Критерий сортировки должен определять «строгую квазиупорядоченность» , которая обладает тремя свойствами: Ассиметричность: n Для оператора <: если выражение x<y истинно, то выражение y<x ложно; n Для предиката op: если выражение op(x, y) истинно, то выражение op(y, x) ложно. Транзитивность: n Для оператора <: если выражения x<y и y<z истинны, то и выражение x<z истинно; n Для предиката op: если выражения op(x, y) и op(y, z) истинны, то и выражение op(x, z) истинно. Иррефлексивность: n Для оператора <: выражение x<x всегда ложно; n Для предиката op: выражение op(x, x) всегда ложно. На основании этих свойств критерий сортировки используется также для проверки на равенство: два элемента равны, если ни один из них не меньше другого (или если ложны оба выражения op(x, y) и op(y, x))
Свойства множеств и мультимножеств Внутренне множества и мультимножества организованы в виде сбалансированного двоичного дерева поиска. Поэтому операции поиска значения в множестве выполняются быстро. Множества и мультимножества не поддерживают прямое обращение к элементам. При косвенном обращении через итераторы действует ограничение: с точки зрения итератора значение элемента является константным.
Операции над множествами и мультимножествами set c; Создает пустое множество или мультимножество set c(op); Создает пустое множество или мультимножество, использующее критерий сортировки op set c 1(c 2); Создает копию другого множества или мультимножества того же типа set c(beg, end); Создает множество или мультимножество, инициализированное элементами интервала [beg, end) set c(beg, end, op); Создает множество или мультимножество с критерием сортировки op, инициализированное элементами интервала [beg, end) c. ~set(); Уничтожает все элементы и освобождает память
Операции над множествами и мультимножествами В таблице символами «set» обозначена одна из следующих конструкций: n set<Elem> - множество с сортировкой по критерию less; n set<Elem, op> - множество с сортировкой по критерию op; n multiset<Elem> - множество с сортировкой по критерию less; n multiset<Elem, op> - множество с сортировкой по критерию op. Существуют два варианта определения критерия сортировки: n в параметре шаблона, n в параметре конструктора.
Операции над множествами и мультимножествами Специальные операции поиска count(elem) Возвращает количество элементов со значением elem find(elem) Возвращает позицию первого элемента со значением elem (или end()) lower_bound(elem) Возвращает первую позицию, в которой может быть вставлен элемент elem (первый элемент > elem) upper_bound(elem) Возвращает последнюю позицию, в которой может быть вставлен элемент elem (первый элемент > elem) equal_range(elem) Возвращает первую и последнюю позиции, в которых может быть вставлен элемент elem (интервал, на котором элементы == elem)
Обработка исключений В общем случае множества и мультимножества поддерживают те же принципы обработки и генерации исключения, что и последовательные контейнеры (вектор, список, дек), т. е. если происходит исключение, то функция либо ничего не делает, либо не изменяет состояние контейнера. Но операции групповой вставки выполняются иначе: каждый элемент заносится в структуру независимо от других элементов. Т. е. при помещении в множество трех элементов, если при помещении второго элемента происходит исключение, то первый элемент из контейнера уже не удаляется.
Отображения и мультиотображения Элементами отображений и мультиотображений являются пары «ключ/значение» . Сортировка элементов выполняется автоматически на основании критерия сортировки, применяемого к ключу. Отображение 1 a 3 2 n z 1 4 5 Мультиотображение a s a 1 2 n z 4 2 a s
Описание классов map и multimap template< class Key, class T, class Compare=less<Key>, class Allocator=allocator<pair<const Key, T> > > class map; template< class Key, class T, class Compare=less<Key>, class Allocator=allocator<pair<const Key, T> > > class multimap; Первый аргумент шаблона определяет тип ключа, в второй – тип значения. Элементы отображения и мультиотображения могут состоять из произвольных типов Key и T, удовлетворяющих двум условиям: n пара «ключ/значение» должна поддерживать присваивание; n ключ должен быть совместим с критерием сортировки.
Операции над отображениями и мультиотображениями map c; Создает пустое отображение или мультиотображение map c(op); Создает пустое отображение или мультиотображение, использующее критерий сортировки op map c 1(c 2); Создает копию другого отображения или мультиотображения того же типа map c(beg, end); Создает отображение или мультиотображение, инициализированное элементами интервала [beg, end) map c(beg, end, op); Создает отображение или мультиотображение с критерием сортировки op, инициализированное элементами интервала [beg, end) c. ~ map(); Уничтожает все элементы и освобождает память
Операции над отображениями и мультиотображениями Специальные операции поиска count(key) Возвращает количество элементов с ключом key find(key) Возвращает позицию первого элемента с ключом key (или end()) lower_bound(key) Возвращает первую позицию, в которой может быть вставлен элемент с ключом key (первый элемент с ключом > key) upper_bound(key) Возвращает последнюю позицию, в которой может быть вставлен элемент с ключом key (первый элемент с ключом > key) equal_range(key) Возвращает первую и последнюю позиции, в которых может быть вставлен элемент с ключом key (интервал, на котором ключи элементов == key)
Операции над отображениями и мультиотображениями Вставка и удаление элементов осуществляется с помощью методов insert и erase. Существуют три способа передачи значения при вставке нового элемента: n использование обозначения value_type, n использование типа pair, n использование функции make_pair. Примеры: map<string, int> coll; coll. insert(map<string, int>: : value_type(“key”, 10)); coll. insert(pair<string, int>(“key”, 10)); coll. insert(make_pair(“key”, 10)); Для отображений и мультиотображений перегружена операция [], которая возвращает значение ассоциированное с ключом.
Итераторы Итератор – объект, предназначенный для последовательного перебора элементов контейнера. Перебор осуществляется через единый интерфейс, основой для которого стал интерфейс указателей. Категории итераторов: n итератор ввода, n итератор вывода, n прямой итератор, n двусторонний (двунаправленный) итератор, n итератор произвольного доступа.
Характеристики итераторов Категория Возможности Поддержка Итератор ввода Чтение в прямом направлении Потоковый итератор ввода Итератор вывода Запись в прямом направлении Потоковый итератор вывода, итератор вставки Прямой итератор Чтение и запись в прямом направлении Двусторонний итератор Чтение и запись в прямом и обратном направлениях Списки, множества, мультимножества, отображения, мультиотображения Итератор произвольного доступа Чтение и запись с произвольным доступом Вектор, дек, строка, массив
Итераторы ввода Итератор ввода перемещается только вперед и поддерживает только чтение. Итератор ввода читает элементы только один раз. Операции: n *iter – обращение к элементу, n iter->member – обращение к атрибуту или методу элемента, n ++iter – смещение вперед (возвращает новую позицию), n Iter++ – смещение вперед (возвращает старую позицию), n iter 1==iter 2 – проверка двух итераторов на равенство, n iter 1!=iter 2 – проверка двух итераторов на неравенство, n TYPE(iter) – копирование итератора (копирующий конструктор).
Итератор вывода Итератор ввода перемещается только вперед и поддерживает только запись. Присваивание новых значений выполняется только для отдельных элементов. Итератор вывода не может использоваться для повторного перебора интервала. Операции: n *iter=value – записывает value в позицию итератора, n ++iter – смещение вперед (возвращает новую позицию), n Iter++ – смещение вперед (возвращает старую позицию), n TYPE(iter) – копирование итератора (копирующий конструктор).
Итераторы ввода и вывода Как правило итераторы предоставляют больше возможностей чем просто итератор ввода или итератор вывода. Примером «чистого» итератора ввода является итератор, читающий данные из стандартного входного потока данных (обычно клавиатуры). Примером «чистого» итератора вывода является итератор для записи в стандартный выходной поток данных.
Прямые итераторы представляют собой комбинацию итераторов ввода и итераторов вывода. В отличие от последних прямой итератор могут ссылаться на один и тот же элемент коллекции и обрабатывать его по нескольку раз. Операции: n *iter – обращение к элементу, n iter->member – обращение к атрибуту или методу элемента, n ++iter – смещение вперед (возвращает новую позицию), n Iter++ – смещение вперед (возвращает старую позицию), n iter 1==iter 2 – проверка двух итераторов на равенство, n iter 1!=iter 2 – проверка двух итераторов на неравенство, n TYPE(iter) – копирование итератора (копирующий конструктор), n iter 1=iter 2 – присваивание итераторов.
Прямые итераторы Прямой итератор не обладает всеми свойствами итератора вывода. Это обусловлено следующими причинами: Итераторы вывода позволяют записывать данные без проверки конца последовательности. Следующий код корректен для итератора вывода, но не для прямого итератора: while(true){ *pos = foo(); ++pos; } n При работе с прямыми итераторами перед разыменованием необходимо заранее убедиться в том, что это возможно. Следующий код некорректен для итераторов вывода, но корректен для прямых итераторов. while(pos != coll. end()){ *pos = foo(); ++pos; } n
Двунаправленные итераторы Двунаправленными итераторами называются прямые итераторы, поддерживающие возможность перебора элементов в обратном порядке. Дополнительные операции: n --iter – смещение назад (возвращает новую позицию), n Iter-- – смещение назад (возвращает старую позицию).
Итераторы произвольного доступа Итераторами произвольного доступа называются двунаправленные итераторы, поддерживающие прямой доступ к элементам. Итераторы произвольного доступа поддерживаются следующими объектами и типами: n контейнеры с произвольным доступом (vector, deque), n строки (string, wstring), n обычные массивы (указатели).
Итераторы произвольного доступа n n n n n iter[n] – обращение к элементу с индексом n, iter+=n – смещение на n элементов вперед (или назад, если n<0), iter-=n – смещение на n элементов назад (или вперед, если n<0), iter+n – возвращает итератор для n-го элемента вперед от текущей позиции, Iter-n – возвращает итератор для n-го элемента назад от текущей позиции, iter 1 -iter 2 – возвращает расстояние между iter 1 и iter 2, iter 1<iter 2 – проверяет, что iter 1 меньше iter 2, iter 1>iter 2 – проверяет, что iter 1 больше iter 2, iter 1<=iter 2 – проверяет, что iter 1 предшествует или совпадает с iter 2, Iter 1>=iter 2 – проверяет, что iter 1 не предшествует iter 2.
Вспомогательные функции итераторов Стандартная библиотека С++ содержит три вспомогательные функции для работы с итераторами: n n n advance(), distance(), iter_swap().
Вспомогательные функции итераторов Функция advance перемещает итератор, передаваемый ей в качестве аргумента. Смещение может выполняться как в прямом, так и обратном направлении сразу на несколько элементов. #include<iterator> void advance(Input. Iterator &pos, Dist n); n n Перемещает итератор ввода pos на n элементов вперед (или назад). Для двунаправленных итераторов и итераторов произвольного доступа значение n может быть отрицательным (перемещение в обратном направлении). Dist – тип шаблона. Обычно является целым типом. Функция не проверяет выход за пределы шаблона.
Вспомогательные функции итераторов Функция distance вычисляет разность между двумя итераторами. #include<iterator> Dist distance(Input. Iterator &pos 1, Input. Iterator &pos 2); Возвращает расстояние между итераторами ввода pos 1 и pos 2. n Оба итератора должны ссылаться на элементы одного контейнера. n Если итераторы не являются итераторами произвольного доступа, итератор pos 2 должен быть доступен от pos 1. n Тип возвращаемого значения Dist определяет тип разности в соответствии с типом итераторов: Iterator_traits<Input. Iterator>: : difference_type n
Вспомогательные функции итераторов Функция iter_swap() меняет местами элементы, на которые ссылаются два итератора. #include<iterator> void iter_swap(Forward. Iterator &pos 1, Forward. Iterator &pos 2); n n Меняет местами элементы, на которые ссылаются итераторы pos 1 и pos 2. Итераторы не обязаны относится к одному типу, но значения должны быть совместимы по присваиванию.
Адаптеры итераторов Обратные итераторы – адаптеры, переопределяющие операторы ++ и – так, что перебор элементов производится в обратном направлении. Получить обратный итератор контейнера можно с помощью методов rbegin() и rend(). Обратить итератор можно с помощью метода reverse_iterator или с помощью функции base().
Адаптеры итераторов Оператор вставки – адаптер итератора, преобразующий присваивание нового значения во вставку нового значения. Разновидности итераторов вставки: n конечный итератор вставки (класс back_insert_iterator, создание back_inserter(cont)), n начальный итератор вставки (класс front_insert_iterator, создание front_inserter(cont)), n общий итератор вставки (класс insert_iterator, создание inserter(cont)).
Адаптеры итераторов Потоковый итератор – адаптер итератора, использующий поток данных в качестве источника или приемника алгоритма. Виды потоковых итераторов: n Потоковые итераторы вывода записывают присваиваемые значения в выходной поток данных. n Потоковые итераторы ввода читают элементы из входного потока данных.
Потоковые итераторы вывода Создание потокового итератора вывода для потока данных ostream: ostream_iterator<T>(ostream) Создание потокового итератора вывода для потока данных ostream с разделением выводимых значений строкой delim (тип const char *): ostream_iterator<T>(ostream, delim) n n *iter – фиктивная операция (возвращает iter), iter=value – записывает value в поток данных (с разделителем, если он задан), ++iter – фиктивная операция (возвращает iter), iter++ – фиктивная операция (возвращает iter).
Потоковые итераторы ввода Создание итератора конца потока данных: istream_iterator<T>() Создание потокового итератора ввода для потока istream: istream_iterator<T>(istream) n n n *iter – обращение к ранее прочитанному значению, Iter->member – обращение к атрибуту или методу ранее прочитанного значения, ++iter – читает следующее значение и возвращает итератор для его позиции, iter++ – читает следующее значение, но возвращает итератор для предыдущего значения, iter 1==iter 2 – проверка iter 1 и iter 2 на равенство iter 1!=iter 2 – проверка iter 1 и iter 2 на неравенство
Объекты функций (функтор) Объект функций (функтор) – объект, для которого определен оператор (). class Funtional. Object. Type{ … public: … тип operator() (параметры) { … } }; int main(int argc, char *argv[]) { … Funtional. Object. Type obj; … obj(параметры); … }
Объекты функций (функтор) Преимущества использования функторов: 1. Объект функции ведет себя более «разумно» , потому что может обладать состоянием. Например, можно создать два экземпляра одной функции, представленной объектом функции, которые могут одновременно обладать разными состояниями. 2. Каждый объект функции обладает некоторым типом. Можно передать объект функции шаблону, определив какие-то аспекты его поведения, а контейнеры с разными объектами функций будут считаться относящимися к разным типам. 3. Объект функции обычно работает быстрее указателя на функцию.
Пример использования class Student{ string surname, name; unsigned year; double rate; public: Student(string, unsigned, double); Student(const Student &); ~Student(void); string get. Surname() const; string get. Name() const; unsigned get. Year() const; double get. Rate() const; void operator= (const Student &st); void print(); };
Пример использования class Student. Comparer{ public: bool operator() (const Student &, const Student &); }; bool Student. Comparer: : operator() (const Student &s 1, const Student &s 2) { return (s 1. get. Surname() > s 2. get. Surname() ) || ( (s 1. get. Surname() == s 2. get. Surname() ) && (s 1. get. Name() > s 2. get. Name() ) ); }
Пример использования int main(int argc, char* argv[]) { set<Student, Student. Comparer> lst; //Помещение элементов в множество set<Student, Student. Comparer>: : iterator pos; for(pos = lst. begin(); pos != lst. end(); ++pos){ Student st = *pos; st. print(); } return 0; }
Предикаты Предикатом называется функция или функтор, возвращающий логическое значение (или значение, преобразуемое к bool). Для использования стандартных объектов функций (функторов), необходимо подключить библиотеку <functional>.
Стандартные объекты функций negate<type>() -param plus<type>() param 1 + param 2 minus<type>() param 1 - param 2 multiplies<type>() param 1 * param 2 divides<type>() param 1 / param 2 modulus<type>() param 1 % param 2 equal_to<type>() param 1 == param 2 not_equal_to<type>() param 1 != param 2 less<type>() param 1 < param 2 greater<type>() param 1 > param 2 less_equal<type>() param 1 <= param 2 greater_equal<type>() param 1 >= param 2 logical_not<type>() !param logical_and<type>() param 1 && param 2 logical_or<type>() param 1 || param 2
Функциональные адаптеры Функциональный адаптер – объект, который позволяет комбинировать объекты функций друг с другом, с определенными значениями или со специальными функциями. Функциональные адаптеры также описаны в библиотеке <functional>.
Функциональные адаптеры Стандартные функциональные адаптеры: n bind 1 st(op, value) – фиксирует первый аргумент бинарного функтора (аналог op(value, param) ) n bind 2 nd(op, value) – фиксирует второй аргумент бинарного функтора (аналог op(param, value) ) n not 1(op) – выполняет логическое отрицание для унарного предиката !op(param) n not 2(op) – выполняет логическое отрицание для бинарного предиката !op(param 1, param 2) Пример: поиск первого четного элемента коллекции pos = find_if(coll. begin(), coll. end(), not 1(bind 2 nd(modulus<int>(), 2)))
Функциональные адаптеры для функций классов: n mem_fun_ref(op) – вызов op как функции объекта, n mem_fun(op) – вызов op как функции указателя на объект. Пример: int main(int argc, char* argv[]) { set<Student, Student. Comparer> lst(lst. begin(), lst. end()); //Ввод элементов множества for_each(lst. begin(), lst. end(), mem_fun_ref(&Student: : print)); return 0; }
Функциональные адаптеры для обычных функций: ptr_fun(op) – вызов как функции *op(param) или *op(param 1, param 2). Пример: bool check(const Student &st) { return (st. get. Rate() >= 9. 0); } … set<Student, Student. Comparer>: : iterator pos; pos = find_if(lst 1. begin(), lst 1. end(), ptr_fun(check)); Student st = *pos; st. print(); …
Алгоритмы В библиотеке STL содержится большое количество различных алгоритмов для обработки элементов контейнеров. Для использования алгоритмов необходимо подключить библиотеку <algorithm>. Некоторые алгоритмы, предназначенные для обработки числовых данных, содержатся в библиотеке <numeric>. При работе с алгоритмами часто используются функторы и адаптеры функторов, описанные в библиотеке <functional>.
Алгоритмы Любой алгоритм STL работает с одним или несколькими интервалами, заданными при помощи итераторов. Для первого интервала всегда задаются две границы: начало и конец в формате [beg, end). Для остальных интервалов обычно задается только одна граница – начало интервала. Алгоритмы работают в режиме замены, а не в режиме вставки. Поэтому перед вызовом алгоритма необходимо убедиться, что приемный интервал содержит достаточное количество элементов. Для повышения возможностей и гибкости некоторые алгоритмы позволяют передавать пользовательские операции, которые вызываются при внутренней работе алгоритма. Такие операции оформляются в виде функций или функторов.
Классификация алгоритмов Разные алгоритмы предназначены для решения разных задач, поэтому их можно классифицировать по основным областям применения. В библиотеке STL в названии алгоритмов введены два специальных суффикса: n Суффикс _if – используется при наличии двух похожих форм алгоритма с одинаковым количеством параметров. Первой форме (без суффикса) передается значение, а второй форме (с суффиксом) – функция или функтор. Например алгоритмы find() и find_if(). n Суффикс _copy – означает, что алгоритм не только обрабатывает алгоритмы, но и копирует их в приемный интервал. Например алгоритмы reverse() и reverse_copy().
Классификация алгоритмов Все алгоритмы можно разделить на следующие группы: n не модифицирующие алгоритмы, n алгоритмы удаления, n алгоритмы перестановок, n алгоритмы сортировки, n алгоритмы упорядоченных интервалов, n численные алгоритмы. Некоторые алгоритмы можно отнести к нескольким категориям.
Не модифицирующие алгоритмы сохраняют как порядок следования обрабатываемых элементов, так и их значения. Они работают с итераторами ввода и прямыми итераторами, поэтому они могут вызываться для всех стандартных контейнеров. for_each() Выполняет операцию с каждым элементом контейнера count() Возвращает количество элементов count_if() Возвращает количество элементов, удовлетворяющих заданному критерию min_element() Возвращает элемент с минимальным значением max_element() Возвращает элемент с максимальным значением find() Ищет первый элемент с заданным значением find_if() Ищет первый элемент, удовлетворяющий заданному критерию
Не модифицирующие алгоритмы search_n() Ищет первые n последовательных элементов с заданными свойствами search() Ищет первое вхождение подинтервала find_end() Ищет последнее вхождение подинтервала find_first_of() Ищет первый из нескольких возможных элементов adjacent_find() Ищет два смежных элемента, равных по заданному критерию equal() Проверяет, равны ли два интервала mismatch() Возвращает первый различающийся элемент в двух интервалах lexicographical_compare() Проверяет, что один интервал меньше другого по лексикографическому критерию
Модифицирующие алгоритмы изменяют значения элементов. Модификация производится непосредственно внутри интервала или в процессе копирования в другой интервал. Если элементы копируются в приемный интервал, то исходный интервал остается без изменений. for_each() Выполняет операцию с каждым элементом copy() Копирует интервал, начиная с первого элемента copy_backwards() Копирует интервал, начиная с последнего элемента transform() Модифицирует (и копирует) элементы, объединяет элементы двух интервалов merge() Производит слияние двух интервалов swap_ranges() Меняет местами элементы двух интервалов
Модифицирующие алгоритмы fill() Заменяет каждый элемент заданным значением fill_n() Заменяет n элементов заданным значением generate() Заменяет каждый элемент результатом операции generate_n() Заменяет n элементов результатом операции replace() Заменяет элементы с заданным значением другим значением replace_if() Заменяет элементы, соответствующие критерию, заданным значением replace_copy() Заменяет элементы с заданным значением при копировании интервала replace_copy_if() Заменяет элементы, соответствующие критерию, при копировании интервала
Модифицирующие алгоритмы Алгоритму for_each() передается операция, которая модифицирует его аргумент. Это означает, что аргумент должен передаваться по ссылке. Алгоритм transform() использует операцию, которая возвращает модифицированный аргумент. Результат присваивается исходному элементу. Ассоциативные контейнеры не могут быть приемниками для модифицирующих алгоритмов.
Алгоритмы удаления составляют отдельную группу модифицирующих алгоритмов. Они предназначены для удаления элементов либо в отдельном интервале, либо в процессе копирования в другой интервал. Ассоциативные контейнеры не могут быть приемниками для алгоритмов удаления. remove() Удаляет элементы с заданным значением remove_if() Удаляет элементы по заданному критерию remove_copy() Копирует элементы, значение которых отлично от заданного remove_copy_if() Копирует элементы, не соответствующие заданному критерию unique() Удаляет смежные дубликаты (элементы равные своему предшественнику) unique_copy() Копируцет элементы с удалением смежных дубликатов
Алгоритмы перестановок Алгоритмами перестановок называют алгоритмы, изменяющие порядок следования элементов (но не их значения) посредством присваивания и перестановки их значения. reverse() Переставляет элементы в обратном порядке reverse_copy() Копирует элементы, переставленные в обратном порядке rotate() Производит циклический сдвиг элементов rotate_copy() Копирует элементы с циклическим сдвигом next_permutation() Переставляет элементы prev_permutation() Переставляет элементы random_shuffle() Переставляет элементы в случайном порядке partition() Изменяет порядок следования элементов так, что элементы соответствующие критерию, оказываются спереди stable_partition() То же, но сохранением относительного расположения элементов
Алгоритмы сортировки sort() Сортирует все элементы (алгоритм quicksort) stable_sort() Сортирует с сохранением порядка следования равных элементов (алгоритм mergesort) partial_sort() Сортирует до тех пор, пока первые n элементов не будут упорядочены (алгоритм heapsort) nth_element() Сортирует элементы слева и справа от элемента в позиции n make_heap() Преобразует интервал в кучу push_heap() Добавляет элемент в кучу pop_heap() Удаляет элемент из кучи sort_heap() Сортирует кучу
Алгоритмы упорядоченных интервалов binary_search() Проверяет, содержит ли интервал заданный элемент includes() Проверяет, что каждый элемент интервала также является элементом другого интервала lower_bound() Находит первый элемент со значением, большим либо равным заданному upper_bound() Находит первый элемент со значением, большим заданного equal_range() Возвращает количество элементов в интервале, равных заданному merge() Выполняет слияние элементов двух интервалов set_union() Вычисляет упорядоченное объединение двух интервалов set_intersection() Вычисляет упорядоченное пересечение двух интервалов set_difference() Вычисляет упорядоченный интервал, содержащий разность двух интервалов set_symmetric_differense() Вычисляет упорядоченный интервал, содержащий исключающую разность двух интервалов inplace_merge() Выполняет слияние двух последовательных, упорядоченных интервалов
Численные алгоритмы выполняют разнообразную обработку числовых элементов. Алгоритмы accumulate и inner_product вычисляют и возвращают сводное значение без модификации интервалов. Другие алгоритмы записывают свои результаты в приемный интервал. accumulate() Объединяет все значения элементов (вычисляет сумму, произведение и т. д. ) inner_product() Объединяет все элементы двух интервалов adjacent_difference() Объединяет каждый элемент с его предшественником partial_sum() Объединяет каждый элемент со всеми предшественниками
Пример bool cmp 1(const Student &st 1, const Student &st 2) { return (st 1. get. Rate() > st 2. get. Rate()); } bool cmp 2(const Student &st 1, const Student &st 2) { return (st 1. get. Rate() < st 2. get. Rate()); } int main(int argc, char* argv[]) { vector<Student> lst; //Ввод списка значений int dir; cout << "Укажите направление сортировки (0 -возр. , !0 – убыв. ): "; cin >> dir; if(dir) sort(lst. begin(), lst. end(), cmp 1); else sort(lst. begin(), lst. end(), cmp 2); for_each(lst. begin(), lst. end(), mem_fun_ref(&Student: : print)); return 0; }
Пример int main(int argc, char* argv[]) { list<Student> lst; //Ввод списка элементов int dir; cout << " Укажите направление сортировки (0 -возр. , !0 – убыв. ): "; cin >> dir; if(dir) lst. sort(cmp 1); else lst. sort(cmp 2); for_each(lst. begin(), lst. end(), mem_fun_ref(&Student: : print)); return 0; }