11.3.2. Добавление элементов
Функция-член insert() (табл. 11.4) добавляет один элемент или диапазон элементов в контейнер. Поскольку карта и набор (и их неупорядоченные версии) содержат уникальные ключи, попытка вставки уже присутствующего элемента не имеет никакого эффекта:
vector<int> ivec = {2,4,6,8,2,4,6,8}; // ivec содержит
// восемь элементов
set<int> set2; // пустой набор
set2.insert(ivec.cbegin(), ivec.cend()); // set2 имеет четыре элемента
set2.insert({1,3,5,7,1,3,5,7}); // теперь set2 имеет восемь элементов
Таблица 11.4. Функция insert() ассоциативного контейнера
с.insert(v) с.emplace(args) v — объект типа value_type; аргументы args используются при создании элемента. Элементы карты и набора вставляются (или создаются), только если элемента с данным ключом еще нет в контейнере с. Возвращает пару, содержащую итератор на элемент с заданным ключом и логическое значение, указывающее, был ли вставлен элемент. У контейнеров multimap и multiset осуществляется вставка (или создание) заданного элемента и возвращение итератора на новый элемент с.insert(b, e) с.insert(il) Итераторы b и е обозначают диапазон значений типа с::value_type; il — заключенный в скобки список таких значений. Возвращает void. У карты и набора вставляются элементы с ключами, которых еще нет в контейнере с. У контейнеров multimap и multiset вставляются все элементы диапазона c.insert(p, v) с.emplace(p, args) Подобны функциям insert(v) и emplace(args), но используют итератор p как подсказку для начала поиска места хранения нового элемента. Возвращает итератор на элемент с заданным ключомВерсии функции insert(), получающие пару итераторов или список инициализации, работают подобно соответствующим конструкторам (см. раздел 11.2.1), но добавляется только первый элемент с заданным ключом.
Добавление элементов в карту
При вставке в карту следует помнить, что типом элемента является pair. Зачастую объекта pair, подлежащего вставке, нет. В этом случае пара создается в списке аргументов функции insert():
// четыре способа добавления слова в word_count
word_count.insert({word, 1});
word_count.insert(make_pair(word, 1));
word_count.insert(pair<string, size_t>(word, 1));
word_count.insert(map<string, size_t>::value_type(word, 1));

map<string, size_t>::value_type(s, 1)
Он создает новый объект пары соответствующего типа для вставки в карту.
Проверка значения, возвращаемого функцией insert()
Значение, возвращенное функцией insert() (или emplace()), зависит от типа контейнера и параметров. Для контейнеров с уникальными ключами есть версии функций insert() и emplace(), которые добавляют один элемент и возвращают пару, сообщающую об успехе вставки. Первая переменная-член пары — итератор на элемент с заданным ключом; второй — логическое значение, указывающее на успех вставки элемента. Если такой ключ уже был в контейнере, то функция insert() не делает ничего, а логическая часть возвращаемого значения содержит false. Если такой ключ отсутствовал, то логическая часть содержит значение true.
Для примера перепишем программу подсчета слов с использованием функции insert():
// более корректный способ подсчета слов во вводе
map<string, size_t> word_count; // пустая карта строк и чисел
string word;
while (cin >> word) {
// вставляет элемент с ключом, равным слову, и значением 1;
// если слово уже есть в word_count, insert() не делает ничего
auto ret = word_count.insert({word, 1});
if (!ret.second) // слово уже было в word_count
++ret.first->second; // приращение счетчика
}
Для каждой строки word осуществляется попытка вставки со значением 1. Если слово уже находится в карте, ничего не происходит. В частности, связанный со словом счетчик остается неизменным. Если слова еще нет в карте, оно добавляется, а значение его счетчика устанавливается в 1.
Оператор if проверяет логическую часть возвращаемого значения. Если это значение false, то вставка не произошла. Следовательно, слово уже было в карте word_count, поэтому следует увеличить значение связанного с ним счетчика.
Еще раз о синтаксисе
Оператор приращения счетчика в этой версии программы подсчета слов трудно понять. Разобрать это выражение будет существенно проще, если сначала расставить скобки в соответствии с приоритетом (см. раздел 4.1.2) операторов:
++((ret.first)->second); // эквивалентное выражение
Рассмотрим это выражение поэтапно.
• ret — пара, содержащая значение, возвращаемое функцией insert().
• ret.first — первая переменная-член пары, на которую указывает итератор карты, с данным ключом.
• ret.first-> — обращение к значению итератора, позволяющее получить этот элемент. Элементы карты также являются парами.
• ret.first->second — та часть пары элемента карты, которая является значением.
• ++ret.first->second — инкремент этого значения.
Таким образом, оператор инкремента получает итератор для элемента с ключом слова и увеличивает счетчик, связанный с ключом, для которого не удалась попытка вставки.
Для читателей, использующих устаревший компилятор или код, предшествующий новому стандарту, объявление и инициализация пары ret также не совсем очевидны:
pair<map<string, size_t>::iterator, bool> ret =
word_count.insert(make_pair(word, 1));
Здесь определяется пара, вторая переменная-член которой имеет тип bool. Понять тип первой переменной-члена этой пары немного труднее. Это тип итератора, определенный типом map<string, size_t>.
Добавление элементов в контейнеры multiset и multimap
Работа программы подсчета слов зависит от того факта, что каждый ключ может присутствовать только однажды. Таким образом, с любым словом будет связан только один счетчик. Но иногда необходима возможность добавить дополнительные элементы с тем же ключом. Например, могло бы понадобиться сопоставить авторов с названиями написанных ими книг. В данном случае для каждого автора могло бы быть несколько записей, поэтому будет использован контейнер multimap, а не map. Поскольку ключи контейнеров multi не должны быть уникальным, функция insert() для них всегда вставляет элемент:
multimap<string, string> authors;
// добавляет первый элемент с ключом Barth, John
authors.insert({"Barth, John", "Sot-Weed Factor"});
// ok: добавляет второй элемент с ключом Barth, John
authors.insert({"Barth, John", "Lost in the Funhouse"});
У контейнеров, допускающих совпадение ключей, функция insert() получает один элемент и возвращает итератор на новый элемент. Нет никакой необходимости возвращать логическое значение, поскольку в эти контейнеры функция insert() всегда добавляет новый элемент.
Упражнения раздела 11.3.2
Упражнение 11.20. Перепишите программу подсчета слов из раздела 11.1 так, чтобы использовать функцию insert() вместо индексации. Какая версия программы по-вашему проще? Объясните почему.
Упражнение 11.21. С учетом того, что word_count является картой типов string и size_t, а также того, что word имеет тип string, объясните следующий цикл:
while (cin >> word)
++word_count.insert({word, 0}).first->second;
Упражнение 11.22. С учетом, что map<string, vector<int>>, напишите типы, используемые как аргументы, и возвращаемое значение версии функции insert(), вставляющей один элемент.
Упражнение 11.23. Перепишите карту, хранящую вектора имен детей с ключом в виде фамилии семьи из упражнений раздела 11.2.1, так, чтобы использовался контейнер multimap.