Лекция 5 Библиотека STL

advertisement
Лекция 5
Библиотека STL
STL - это библиотека стандартных шаблонов. Первая часть библиотеки содержит
разнообразные контейнеры. Это динамические массивы, списки, очереди и др. Сюда же
относятся так называемые ассоциативные контейнеры. Основная их отличительная черта - это
то, что хранящиеся в них значения ищутся по ключам. При этом ключ может быть самым
разным. Аналогия такого контейнера из жизни - это телефонная книга. Там номера телефонов
ищутся по фамилии владельца или названия фирмы. В каждом контейнере кроме собственно
данных есть методы для работы с этими данными (для добавления, поиска, удаления и др.).
Вторая часть библиотеки STL - это алгоритмы. Обращаем внимание, что алгоритмы не
являются частью контейнеров, а образуют отдельную подсистему. При этом почти любой
алгоритм может применяться к почти любому контейнеру. То есть вызывая метод для
некоторого алгоритма, мы вызываем этот метод сам по себе, а не для экземпляра некоторого
класса. Контейнер же, к которому применяется алгоритм, передается в качестве параметра.
Третья часть STL - это итераторы. Итератор - это некоторый указатель, который может
пробегать все элементы контейнера. Через итератор мы можем получить некоторый элемент
контейнера. Итераторы бывают разных типов: для движения только вперед, для движения в
обе стороны и др.
Пример использования контейнера vector:
#include <iostream>
// Добавляем нужную библиотеку.
#include <vector>
using namespace std;
void main()
{
// Объявляем вектор из целых чисел.
vector <int> k;
// Добавляем элементы в конец вектора.
k.push_back(22);
k.push_back(11);
k.push_back(4);
// Показываем все элементы вектора.
for (int i = 0; i<k.size(); i++)
{
cout<<k[i]<<"\n";
}
cout<<"***\n";
// Удаляем элемент с конца вектора.
k.pop_back();
// Показываем все элементы вектора.
for (i = 0; i<k.size(); i++)
{
cout<<k[i]<<"\n";
}
cout<<"***\n";
// Удаляем все элементы ветораю
k.clear();
// Проверяем, что вектор пуст.
if(k.empty())
{
cout<<"Vector is empty\n";
}
}
Использованные методы и переменные шаблона vector (push_back, pop_back, clear и
empty) достаточно ясны из комментариев. Обратите еще внимание, что для доступа к
отдельным элементам вектора используется оператор [] - как и для элементов массива. Также
обратите внимание, что мы должны подключить библиотеку vector.
1. Контейнеры
Основные контейнеры библиотеки STL:
vector – динамический массив. Возможен произвольный доступ к элементам через
индекс
list - двунаправленный список. Доступ к элементам возможен только последовательный с
двух сторон списка.
stack - стек.
queue - очередь.
deque - двусторонняя очередь.
map - ассоциативный список. Хранит данные в виде пары Ключ-Значение (с каждый
ключом связано только одно значение).
multimap - ассоциативный список. Хранит данные в виде пары Ключ-Значение (с
каждый ключом связано только несколько значений).
set - множество. Все его элементы уникальны.
multiset - множество. В нем могут быть совпадающие элементы.
bitset - набор битов (каждый из которых отвечает за отсутствие / наличие чего-либо).
Для использования этих контейнеров необходимо написать в начале программы нужный
include. Как правило, include пишется такой же, как имя шаблона. Два исключения - для
использования multimap надо в include добавить map и для multiset - set.
2. Итераторы
Итераторы являются собой, можно сказать, указателями на переменную. Они знают, где
находится необходимая нам переменная и могут "добыть" её из памяти. Итераторы в основном
используются для операции с элементами контейнеров: сортировка, поиск, копирование и т.д.
Для создание итератора необходимо написать
имя контейнера <тип данных> :: iterator имя итератора.
Например,
vector <float>::iterator begin;
Теперь что мы можем делать с итераторами. Мы можем получить элемент, на который
они ссылаются с помощью операции разыменования:
cout<<*cur<<endl;
Здесь мы выводим элемент, на который указывает cur. Оператор * позволяет нам
обращаться не к итератору, а к элементу, на который он указывает.
Мы можем перейти к итератору на следующий элемент или дальше:
cur++; // перейти к следующему элементу
cur+=10; // <=>cur=cur+10 перейти на 10 элементов вперед
Методы итераторов:
begin() – указывает на первый элемент
end() – указывает на элемент, следующий за последним
rbegin() - указывает на первый элемент в обратной последовательности
rend() – указывает на элемент, следующий за последним, в обратной последовательности
Вот, например, вывод всего вектора на экран:
vector <string> test;
//как-то его заполнили
vector <string>::iterator cur;
for (cur=test.begin();cur<test.end();cur++)
cout<<*cur<<endl;
Еще пример кода:
#include <iostream>
#include <vector>
using namespace std;
void main()
{
// Объявляем вектор из целых.
vector <int> k;
// Добавляем элементы в конец вектора.
k.push_back(22);
k.push_back(11);
k.push_back(4);
// Объявляем итератор.
vector <int>::iterator p;
// Устанавливаем итератор в начало
// и передвигаем его в цикле
// на следующую позицию.
for (p = k.begin(); p < k.end(); p++)
{
// Выводим содержимое элементов вектора
// через разименованный итератор.
cout<<*p<<"\n";
}
}
3. Алгоритмы
Рассмотрим мы применение алгоритмов на примере сортировки элементов вектора. Вот так
будет выглядеть программа, которая сортирует элементы вектора:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void main()
{
// Объявляем вектор из целых.
vector <int> k;
// Добавляем элементы в конец вектора.
k.push_back(22);
k.push_back(11);
k.push_back(4);
k.push_back(100);
vector <int>::iterator p;
// Вывод неотсортированного вектора.
for (p = k.begin(); p<k.end(); p++)
{
cout<<*p<<"\n";
}
// Сортировка вектора.
sort(k.begin(), k.end());
// Вывод отсортированного вектора.
cout<<"sorted:\n";
for (p = k.begin(); p<k.end(); p++)
{
cout<<*p<<"\n";
}
}
При вызове функции sort мы указываем, от какого и до какого элемента мы выполняем
сортировку. В нашем случае мы сортируем наш вектор от начала и до конца. Результатом
выполнения программы будет вывод числе 4, 11, 22, 100 (именно в таком, отсортированном
порядке). Обратите внимание, что функция sort не принадлежит нашему вектору. Т. е. мы не
пишем что-то вроде
...
k.sort(...);
...
Т. е. наш метод сортировки (как и другие алгоритмы) образует отдельную подсистему в
библиотеке STL (наряду, например, с контейнерами - тем же вектором, например).
Последовательные контейнеры
К последовательным контейнерам относятся векторы (vector), двусторонние очереди
(deque) и списки (list). К адаптерам последовательных контейнеров относятся стек (stack) и
очередь (queue).
Вектор – это структура, эффективно реализующая произвольный доступ к элементам, а
также добавление в конец и удаление из конца. Элементы вектора хранятся в непрерывном
участке памяти.
Двусторонняя очередь (дек) эффективно реализует произвольный доступ к элементам, а
также добавление в оба конца и удаление из обоих концов. Дек не гарантируется
расположение своих элементов в непрерывных участках памяти.
Список эффективно реализуется вставку и удаление в произвольном месте, но не имеет
произвольного доступа к своим элементам. Элементы списка хранятся в произвольных
участках памяти.
Для всех последовательных контейнеров реализованы следующие методы:
Операция
Функция
vector
deque
list
Вставить
в конец
push_back
+
+
+
Удалить
в конце
pop_back
+
+
+
Вставить
в начале
push_front
-
+
+
pop_front
-
+
+
Вставить
в
любом месте
insert
(+)
(+)
+
Удалить
в
любом месте
erase
(+)
(+)
+
Удалить
начале
в
Операции вставки элемента в произвольное место эффективно работают только в
контейнере list.
1. Контейнер vector
Для создания вектора вам необходимо подключить <vector>. Затем создание вектора
почти ничем не отличается от создания переменной и/или массива:
vector <тип элементов> имя_вектора;
Для записи в вектор достаточно набрать
имя вектора.push_back(значение)
При выполнении этой операции будет происходить динамическое выделение памяти под
данный элемент вектора и занесение в эту область памяти указанного значения.
Например:
vector <int> test;
test.push_back(10);
test.push_back(20);
Обращение к n-ому элементу ничем не отличается от обращения к элементу массива:
test[0]++;
cout<<test[1];
test[1]=222;
Для удаления последнего элемента вектора используется функция pop_back()
test.pop_back();
Еще ряд функций:













test.at(i) - равносильно записи test[i], но при этом, если i-ого элемента не существует,
программа не вылетит
test.asign(n,m) - записывает в массив n элементов со значением m
test.asign(start,end) - записывает в вектор значения от start до end (start и end - итераторы
на элементы другого вектора).
test.front() - возвращает ссылку на первый элемент
test.back() - возвращает ссылку на последний элемент
test.begin() - возвращает итератор первого элемента вектора
test.end() - возвращает итератор последнего элемента вектора
test.clear() - очищает вектор
test.erase(i) или test.erase(start,end) - удаляет элемент с итератором i или элементы с
интераторами между start и end
test.size() - возвращает количество элементов в векторе
test.swap(test2) - меняет местами содержимое вектора test и вектора test2
test.insert(a,b) - вставляет в вектор test переменную b перед элементом с итератором a и
возвращает итератор вставленного элемента
test.insert(a,n,b) - вставляет n копий b перед элементом с итератором a
2. Контейнер deque
Подключение библиотеки:
deque <тип элементов> имя_дека;
Типовые операции:
 front – возврат значения первого элемента;
 back – возврат значения последнего элемента;
 push_front – добавление элемента в начало;
 push_back – добавление элемента в конец;
 pop_front – удаление первого элемента;
 pop_back – удаление последнего элемента;
 size – возврат числа элементов дека;
 clear – очистка дека.
Пример. Создать дек и вывести его на экран двумя способами (через указатель и через
прямой доступ)
#include <iostream>
#include <deque>
#include <iterator>
// подключаем заголовочный файл деков
// заголовок итераторов
using namespace std;
int main()
{
setlocale(LC_ALL,"Rus");
int dequeSize = 0,k;
cout << "Введите размер дека: ";
cin >> dequeSize;
deque <int> myDeque;
// создаем дек и резервируем для него память
deque <int>::iterator out;
cout << "Введите элементы дека: ";
// заполняем дек с клавиатуры
for (int i = 0; i < dequeSize; i++) {
cin >> k;
myDeque.push_back(k);
}
cout << "\nВведенный дек: ";
out=myDeque.begin();
for (out = myDeque.begin(); out < myDeque.end(); out++)
{
cout<<*out;
}
cout<<endl;
for (int i = 0; i < dequeSize; i++)
{
cout<<myDeque[i];
}
return 0;
}
3. Контейнер list (двусвязный список)
По своей структуре списки сильно отличаются от векторов и деков, хотя они
поддерживают почти весь набор операций характерных для деков и векторов. Однако, кроме
того, в списках есть набор специфических функций. Чтобы воспользоваться контейнером
списков в С++, вам необходимо подключить следующий заголовочный файл:
#include <list>
Дополнительные функции:
Сцепка списков (splice) служит для перемещения элементов из одного списка в другой
без перераспределения памяти, только за счет изменения указателей:
splice(позиция начала вставки, имя списка)
splice(позиция начала вставки, имя второго списка, итератор
вставляемого элемента);
splice(позиция начала вставки, имя второго списка, итератор 1
вставляемого элемента, итератор 2 вставляемого элемента);
Оба списка должны содержать элементы одного типа. Первая форма функции вставляет
в вызывающий список перед элементом, позиция которого указана первым параметром, все
элементы списка, указанного вторым параметром, например:
list <int> LI, L2;
... // Формирование списков
Ll.splice(L1.begin()+ 4,L2);
Второй список остается пустым. Нельзя вставить список в самого себя.
Вторая форма функции переносит элемент, позицию которого определяет третий
параметр, из списка х в вызывающий список. Допускается переносить элемент в пределах
одного списка.
Третья форма функции аналогичным образом переносит из списка в список несколько
элементов. Их диапазон задается третьим и четвертым параметрами функции. Если для одного
и того же списка первый параметр находится в диапазоне между третьим и четвертым,
результат не определен.
Пример:
#include <list>
#include <iostream>
using namespace std;
int main()
{
list <int> L1;
list <int>::iterator i, j, k;
for (int i = 0; i<5; i++) L1.push_back(i + 1);
for (int i = 12; i<14; i++) L1.push_back(i);
cout <<"Исходный список: ";
for (i = L1.begin() ; i!= L1.end(); ++i)
cout <<*i<< " ";
cout <<endl;
i = L1.begin();
i++;//указатель на втором элементе
k = L1.end();
j = --k;//указатель на последнем элементе
k++;//указатель за последним элементом
j--;//указатель на предпоследнем элементе
L1.splice(i,L1,j,k);
cout <<"Список после сцепки: ";
for (i = L1.begin(); i != L1.end(); ++i)
cout <<*i <<" ";
}
Исходный список 1 2 3 4 5 12 13
Список после сцепки 1 12 13 2 3 4 5
Для удаления элемента по его значению используется функция
remove(значение)
Если в списке несколько повторяющихся значений, то они будут удалены все.
Для упорядочения списков используется метод sort():
L1.sort();
Метод unique() сортирует список, удаляя из него повторяющиеся элементы.
Для слияния список используется метод
merge(второй список)
Оба сливаемых списка должны быть упорядочены.
Метод reverse() меняет порядок следования элементов на обратный.
Адаптеры последовательных контейнеров
Адаптеры контейнеров
- это контейнеры, созданные на основе существующих
контейнеров и имеющие ограниченный функционал работы. Адаптеры контейнеров не
поддерживают работу с итераторами, то есть в них не допускается перемещение по элементам.
Контейнеры stack и queue созданы на основе контейнера deque и реализуют стандартные
действия для работы со стеком и очередью.
1. Контейнер stack
Для создания стека нужно подключить <stack> и в коде программы его объявить:
stack <type> name, где type - тип стека, а name - имя стека.
У стека есть немного функций:





push() - добавить элемент
pop() - удалить верхний элемент
top() - получить верхний элемент
size() - размер стека
empty() - true, если стек пуст
Пример:
#include <iostream>
#include <string>
#include <stack>
using namespace std;
void main()
{
string s;
stack <string> st;
s="";
while (s.compare("end"))
{
cin>>s;
st.push(s);
}
while (!(st.empty()))
{
cout<<st.top()<<endl;
st.pop();
}
}
В этом примере мы считываем слова с клавиатуры, пока не встретится слово «end» и
выводим их в обратном порядке
к2: 1 6 14 18 5 3 0 13 3 6 8 1 15 13 19
2. Контейнер queue (очередь)
Очереди, как следует из названия, используют принцип first in first out (FIFO). То есть,
тот, кого мы первым запихнули в очередь, первым из нее и выйдет. Реализуются очереди
также просто. Подключаем <queue>. И создаем очередь
queue <type> name;
Перечень функций почти тот же, что и у стека:






push() - добавить элемент
pop() - удалить первый элемент очереди
size() - размер очереди
empty() - true, если очередь пуста
front() - получить первый элемент
back() - получить последний элемент
Пример (аналогичный предыдущему):
#include <iostream>
#include <string>
#include <queue>
using namespace std;
void main()
{
string s;
queue <string> st;
s="";
while (s.compare("end"))
{
cin>>s;
st.push(s);
}
while (!(st.empty()))
{
cout<<st.front()<<endl;
st.pop();
}
}
6 10 11 12 14 15 17 18 5 3 0 13 3 6 8 1 15 13 18 19
Стандартные алгоритмы
Для работы алгоритмов необходимо подключить <algorithm> в начале программы.
Алгоритмы поиска.
Все алгоритмы поиска возвращают итератор на элемент, а не сам элемент.
 find(begin,end,what) - ищет первый элемент со значением what в промежутке
begin - end, где begin и end - итераторы соответствующего контейнера
 adjacent_find(start,end) - ищет два последовательных совпадающих элемента
между start и end и возвращает итератор на него
 search(start,end,sbegin,send) - ищет между start и end последовательность sbeginsend
Алгоритмы сортировки
- инвертирует элементы последовательности start-end (т.е.
сортирует в обратном порядке. Если было последовательность 1 2 3, то в результате
получим 3 2 1)
 random_shuffle(start,end) - сортирует элементы между start и end в случайном
порядке
 sort(start,end) - сортирует элементы от start до end в порядке возврастания.
 reverse(start,end)
Удаления элементов
 remove(begin,end,what) - в промежутке begin-end
 unique(begin,end) - удаляет все дубликаты
удаляет все what
Другие функции
 swap_ranges(start,end,start2) - меняет местами элементы от start до end с
элементами от start2 до end2, end2 рассчитывается автоматом, чтобы промежуток startend был равен промежтку start2-end2
 replace(s,e,d1,d2) - меняет в промежутке s-e все элементы d1 на элементы d2
 fill(begin,end,data) - заполнить промежуток от begin до end значением data
 copy(start,end,new_start)
- копирует промежуток от start до end в new_start (все
параметры - итераторы)
 count(sbegin,send,d) - подсчет количества элементов со значением d в
промежутке sbegin - send
Ассоциативные контейнеры
Как уже указывалось, ассоциативные контейнеры обеспечивают быстрый доступ к
данным за счет того, что они, как правило, построены на основе сбалансированных деревьев
поиска (стандартом регламентируется только интерфейс контейнеров, а не их реализация).
Существует пять типов ассоциативных контейнеров: словари (тар), словари с дубликатами
(multimap), множества (set), множества с дубликатами (multiset) и битовые множества (bitset).
Словари часто называют также ассоциативными массивами или отображениями.
1. Контейнеры map и multimap
В с++ существует контейнер map, который позволяет ассоциировать элементы с чем
угодно. Например, у вас есть список продуктов для покупки (или список книг в библиотеке).
Вам необходимо хранить информацию о том, сколько и чего нужно купить (или название и
количество книг в библиотеке). Контейнер <map> позволяет нам хранить эти данные в виде
ключ-значение (например, книга-количество, продукт-вес). Контейнер <map> является
отсортированным массивом. Сортировка произведена по ключу. При добавлении нового
элемента он автоматически вставляется в массив таким образом, чтобы не нарушилась
сортировка.
Для использования <map> вам необходимо сначала его подключить:
#include <map>
Для создания контейнера достаточно написать:
map <тип ключа,тип данных> имя контейнера;
Для доступа (или записи) в массив нужно писать:
map_name[key];
Контейнер map также имеет возможность работы с итераторами. Поэтому вы можете
использовать с ним множество различных алгоритмов.
Кроме этого, в данном контейнере существуют еще и специальные функции:
begin() - итератор на первый элемент
end() - итератор на элемент за последним
empty() - true, если контейнер пуст
count(key) - число элементов с заданным ключом (в map 1 или 0)
find(key) - итератор на элемент с указанным ключом
erase(it), erase(start,end) - удаляет элемент с заданным итератором или между
заданными
 size() - число элементов
 clear() - полная очистка контейнера






Для обращения к элементу в строках и векторах достаточно было перед именем
контейнера поставить *, но в map так не получится, т.к. в каждом элементе хранится 2
значения (ключ и данные). Поэтому надо писать так:
(*iter).first - для обращения к ключу и
(*iter).second -для обращения к данным,
где iter - итератор элемента
Кроме контейнера map существует контейнер multimap. Его отличие в том, что мы
можем одному ключу задать несколько соответствий. Например, автору сопоставить
несколько написанных им книг. Для его использования необходимо также подключить
библиотеку map, а для создания контейнера написать:
multimap <тип ключа,тип данных> имя контейнера;
Пример. Программf для подсчета количества слов в тексте и вывод частоты их встречи в
процентном соотношении (подсчет частоты встречи слов в тексте в процентах)
#include
#include
#include
#include
<iostream>
<string>
<map>
<fstream>
using namespace std;
int main()
{
map <string,int> words;
ifstream in;
in.open("in.txt");
string word;
while (!in.eof())
{
in>>word; //считывает из файла очередное слово
words[word]++;//ищет запись с ключом word и увеличивает значение поля данных на 1,
если этой записи нет, то она будет создана
}
ofstream out;
out.open("out.txt");
int count=0; //счетчик количества слов
map <string,int>::iterator cur;//создаем итератор
out<<"Words count:"<<endl;
for (cur=words.begin();cur!=words.end();cur++) //вывод списка слов с указанием частоты
встречаемости
{
out<<(*cur).first<<": "<<(*cur).second<<endl;
count+=(*cur).second; //подсчитываем количество слов в переменной count
}
out<<"Words percenc:"<<endl;
for (cur=words.begin();cur!=words.end();cur++) //вывод встречаемости слов в процентном
соотношении
out<<(*cur).first<<": "<<((float)(*cur).second/count)*100<<"%"<<endl;
return 0;
}
2. Контейнеры set и multiset
Множество set является ассоциативным контейнером, который хранит объекты типа
«ключ», то есть в контейнере этого типа не может быть одинаковых элементов. Так же как и
map, контейнер типа set является отсортированным массивом.
Пример
#include <iostream>
#include <set>
using namespace std;
void main ()
{
//======== Создаем множество целых
set <int> s;
s.insert(1);
s.insert(2);
s.insert (3);
//======= Повторно вставляем единицу (она не пройдет)
s.insert (1);
//==== Два раза вставляем "в конец последовательности"
s.insert(--s.end(),4);
s.insert(--s.end(),-1);
set <int>::iterator cur;//создаем итератор
for (cur=s.begin();cur!=s.end();cur++) //вывод
cout<<*cur<<endl;
}
Получим
-1
1
2
3
4
Множество multiset тоже является ассоциативным контейнером, который хранит объекты
типа «ключ», но в контейнере этого типа могут быть одинаковые элементы.
#include <iostream>
#include <set>
using namespace std;
void main ()
{
//======== Создаем множество целых
multiset <int> s;
s.insert(1);
s.insert(2);
s.insert (3);
//======= Повторно вставляем единицу (она не пройдет)
s.insert (1);
//==== Два раза вставляем "в конец последовательности"
s.insert(--s.end(),4);
s.insert(--s.end(),-1);
multiset <int>::iterator cur;//создаем итератор
for (cur=s.begin();cur!=s.end();cur++) //вывод
cout<<*cur<<endl;
}
Получим
-1
1
1
2
3
4
Download