Standart template library (STL). Продолжение.

1. Инвалидация итераторов.

Инвалидацией итератора (от англ. invalid) называется процесс, при котором итератор перестает указывать туда, куда он должен указывать, т.е. итератор становится невалидным.
В разных контейнерах инвалидация происходит в разные моменты:
• List: при удалении элемента происходит инвалидация итераторов, указывающих на этот элемент.
• Vector: при удалении — инвалидация итератора удаляемого элемента и всех после него; при добавлении — инвалидация всех итераторов (т.к. может произойти переаллокация внутреннего буфера). Если при добавлении элемента в конец capacity > size, то инвалидации не происходит.
• Deque: при удалении первого и последнего элементов — инвалидация только соответствующих итераторов; при добавлении или удалении элементов в середине — инвалидация всех итераторов.

List Vector Deque
Удаление Соответствующий итератор Соответствующий итератор и все после него При удалении первого и последнего элементов — только соответствующие итераторы;
при удалении в середине — всех итераторов
Добавление Конкретный итератор Все итераторы (если про capacity и size ничего не известно) см. удаление

2. Ассоциативные контейнеры

a. SET<> упорядоченное множество

Добавление элементов в контейнер осуществляется с помощью операции insert:

 set < int > s;
 s.insert(20);

Операция insert возвращает pair, первым элементом которой является итератор на только что добавленный элемент, либо на элемент с таким же значением (если он уже был добавлен в set). Вторым элементом этой пары является параметр bool, который имеет значение true в случае, если новый элемент был добавлен, и false — если элемент с таким значением уже существовал.
Внутри set организован как сбалансированное дерево (АВЛ-дерево или красно-черное дерево). Соответственно операции insert (вставка) и find (поиск) выполняются за O(logn).
Операцию find можно использовать для проверки наличия элемента в контейнере следующим образом:

if (s.find(10) == s.end()) {...} //т.е. в случае, если элемент не найден find возвращает итератор на конец контейнера

Итератор set-а не позволяет изменять значение, на которое он указывает. Чтобы поменять какой-то элемент, необходимо сперва удалить его с помощью erase, а потом добавить нужный элемент с помощью insert.
В качестве параметра в set может быть использован только тип, для которого определена операция "<":

 set < int > s; //Для int определен оператор "<"

Определить оператор "<" можно самостоятельно, но это позволит упорядочивать только одним способом. Как обойти это ограничение см. ниже.

b. multiset<> мультимножество

Предназначено для хранения объектов,которые могут повторяться.
Multiset очень похоже на set, за исключением операции find: она возвращает итератор на первый (их может быть несколько) найденный объект, если соответствующий элемент найден, и multiset::end — если элемента нет в контейнере.
В связи с тем, что в multiset несколько элементов могут совпадать, для этого контейнера определены несколько дополнительных функций:
• lower_bound возвращает итератор на первый найденный элемент
• upper_bound возвращает итератор на следующий за последним
• equal_range возвращает пару итераторов, задающую промежуток между первым и последним найденным элементами
Чтобы посчитать количество элементов с определенным ключом можно воспользоваться функцией count. Если бы такой функции не было, то достаточно в цикле пробежать от lower_bound до upper_bound и посчитать количество итераций.
Если ни одного элемента не найдено, то lower_bound == upper_bound.
Также можно воспользоваться операцией std::distance, о которой рассказано ниже.
Пример использования:

  multiset < int > myMultiset;
  multiset < int >::iterator it, itLow, itUp;

  for (int i = 1; i < 8; i++) {
	mymultiset.insert(i * 10); // 10 20 30 40 50 60 70
  }
  itLow = myMultiset.lower_bound (30);  
  itUp= myMultiset.upper_bound (40);            

  mymultiset.erase(itLow,itUp);   // стираем все в промежутке от 30 до 40

c. map< , >

Организовано как дерево, которое хранит пары ключ-значений.
Добавление осуществляется следующим образом:

std::map < std::string, size_t > m;
m.insert(std::pair<std::string, int>("Vasia", 100)); // добавление значения 100
с ключом "Vasia"
или

m.insert(std::make_pair("Vasia", 100)); // make_pair сам выводит типы по введенным данным
Изменение элемента осуществляется за O(logn):

m["Vasia"] = 1000;
Поиск элемента возвращает итератор на пару значений:

it = m.find("Vasia");
it -> first; // первое значение элемента, "Vasia"
it -> second;  //второе значение, 1000
В map значение ключа изменить нельзя, так как по первому элементу строится дерево, и поменять ее можно, только удалив элемент и добавив нужный (с помощью erase-insert). Вторую же часть можно менять, т.к. она не влияет на упорядочение в дереве.

d. multimap< , >

Multimap аналогичен multiset, но хранит пары ключ-значения. Отличием от map является то, что он позволяет хранить несколько значений с один ключом.

3.Предикаты и функторы

Пусть у нас есть структура:

struct Person {
   std::string Name;
   std::string University;
   int Age;
}
Допустим, мы хотим уметь сортировать объекты типа Person не только по имени (Name), но и по возрасту (Age).
Как уже было сказано раньше, для этого нам нужно переопределить оператор "<". Если мы поступим таким образом, то это позволит нам сортировать объекты Person только одним способом.

bool operator < (Person const& a, Person const& b) {
  ... //наш вариант сортировки
  return a.name < b.name; // например
}
Так как мы хотим сортировать несколькими способами, то мы воспользуемся другой техникой — предикатами.
Предикат — это структура, с переопределенным operator (), который возращает bool.
Создадим такую структуру:

struct by_name {
  bool operator ()(const Person& p1, const Person& p2) {
    return p1.name < p2.name;
  }
}
Этот предикат задаёт упорядочение объектов типа Person по имени.
В общем случае (когда operator () возвращает не bool) такая структура называется функтором.
Теперь в set в качестве шаблонного параметра мы можем передать структуру by_name, с помощью которой будет сортироваться Person:

set < Person, by_name > s;
Аналогично можно определить структуру by_age, которая будет сортировать объекты типа Person по возрасту, и структуру by_university, которая, соответственно, будет сортировать объекты по университету.
NB! Оператор "<", заданные предикатом, должен быть строгим, т.е. одновременно не должно выполняться следующее: a < b и b < a, если a != b.

4. Итераторы

Итераторы в стандартной библиотеке делятся на несколько категорий:

1. Random-access iterator
Имеют ту же функциональность, что и обычные указатели. Это наиболее полные в плане функциональности итераторы. Есть арифметика итераторов.

2. Bidirectional iterator
Специально разработанные итераторы, предназначенные для последовательного доступа в обоих направлениях: вперед и назад. Только операторы ++ и --.

3. Forward iterator
Предназначены для последовательного доступа от начала к концу. Только оператор ++.

4.1 Output iterator
Предназначены для последовательных операций вывода. К значению, на которое указывает итератор, можно обращаться только на запись.

4.2 Input iterator
Предназначены для последовательных операций ввода. К значению, на которое указывает итератор, можно обращаться только на чтение.

Что еще полезно знать об итераторах:

• iter_swap()
По двум итераторам меняет местами значения, на которые они указывают. Аналогична функции std::swap() для двух значений, но принимает итераторы на эти значения. Функция находится в <algorithm>
std::distance(p, q)
Вычисляет расстояние между двумя итераторами.
Для итераторов типа Random-access используется арифметика итераторов. Для остальных категорий итераторов используются operator++ и operator==
• iterator_traits
Это template class позволяющий получить информацию об итераторе: категорию, тип итерируемых значений и т.д. iterator_traits имеет следующие поля:
::value_type — тип элемента, на который может указывать итератор данного типа,
::pointer, reference — тип указателя/ссылки,
::category — собственно категория итератора.




Конспект выполнен Ухорской Натальей. 7 апреля 2010г.