Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
// четыре способа добавления слова в 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));
Как уже упоминалось, по новому стандарту простейшим способом создания пары является инициализация списком аргументов в фигурных скобках. В качестве альтернативы можно вызвать функцию make_pair() или явно создать пару. Вот аргументы последнего вызова функции insert():
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.
11.3.3. Удаление элементов
Ассоциативные контейнеры определяют три версии функции erase(), описанные в табл. 11.5. Подобно последовательным контейнерам, можно удалить один элемент или диапазон элементов, передав функции erase() итератор или пару итераторов. Эти версии функции erase() подобны соответствующим функциям последовательных контейнеров: указанный элемент (элементы) удаляется и возвращается тип void.
Таблица 11.5. Удаление элементов ассоциативного контейнера
c.erase(k) Удаляет из карты с элемент с ключом k. Возвращает значение типа size_type, указывающее количество удаленных элементов c.erase(p) Удаляет из карты с элемент, обозначенный итератором p. Итератор p должен относиться к фактически существующему элементу карты с, он не может быть равен итератору, возвращаемому функцией c.end(). Возвращает итератор на элемент после позиции p или c.end(), если итератор p обозначает последний элемент контейнера с c.erase(b, е) Удаляет элементы в диапазоне, обозначенном парой итераторов b и е. Возвращает итератор еАссоциативные контейнеры предоставляют дополнительную версию функции erase(), получающую аргумент типа key_type. Эта версия удаляет все элементы, если таковые вообще имеются, с заданным ключом и возвращает количество удаленных элементов. Эту версию можно использовать для удаления определенных слов из контейнера word_count прежде, чем вывести результат:
// удалить по ключу, возвратить количество удаленных элементов
if (word_count.erase(removal_word))
cout << "ok: " << removal_word << " removedn";
else
cout << "oops: " << removal_word << " not found!n";
Для контейнеров с уникальными ключами функция erase() всегда возвращает нуль или единицу. Если возвращается значение нуль, значит, удаляемого элемента не было в контейнере.
Для контейнеров с не уникальными ключами функция erase() возвращает количество удаленных элементов и может быть больше единицы:
auto cnt = authors.erase("Barth, John");
Если authors — это контейнер multimap, созданный в разделе 11.3.2, то переменная cnt будет содержать значение 2.
11.3.4. Индексация карт
Контейнеры map и unordered_map предоставляют оператор индексирования и соответствующую функцию at() (см. раздел 9.3.2), представленные в табл. 11.6. Типы контейнеров set не поддерживают индексацию, поскольку в наборе нет никакого "значения", связанного с ключом. Элементы сами являются ключами, поэтому операция "доступа к значению, связанному с ключом", бессмысленна. Нельзя индексировать контейнер multimap или unordered_multimap, поскольку с заданным ключом может быть ассоциировано несколько значений.