Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
// сортировка по длине слов от коротких к длинным
sort(words.begin(), words.end(), isShorter);
// сортировка по длине слов от длинных к коротким
sort(words.begin(), words.end(), bind(isShorter, _2, _1));
В первом вызове, когда алгоритм sort() должен сравнить два элемента, А и В, он вызовет функцию isShorter(A, В). Во втором вызове аргументы функции isShorter() меняются местами. В данном случае, когда алгоритм sort() сравнивает элементы, он вызывает функцию isShorter(В, А).
Привязка ссылочных параметровПо умолчанию аргументы функции bind(), не являющиеся знакоместами, копируются в возвращаемый ею вызываемый объект. Однако, подобно лямбда-выражениям, иногда необходимо связать аргументы, которые следует передать по ссылке, или необходимо связать аргумент, тип которого не допускает копирования.
Для примера заменим лямбда-выражение, которое захватывало поток ostream по ссылке:
// os - локальная переменная, ссылающаяся на поток вывода
// с - локальная переменная типа char
for_each(words.begin(), words.end(),
[&os, c] (const string &s) { os << s << c; });
Вполне можно написать функцию, выполняющую ту же задачу:
ostream &print(ostream &os, const string &s, char c) {
return os << s << c;
}
Но для замены захвата переменной os нельзя использовать функцию bind() непосредственно:
// ошибка: нельзя копировать os
for_each(words.begin(), words.end(), bind(print, os, _1, ' '));
Поскольку функция bind() копирует свои аргументы, она не сможет скопировать поток ostream. Если объект необходимо передать функции bind(), не копируя, то следует использовать библиотечную функцию ref():
for_each(words.begin(), words.end(),
bind(print, ref(os), _1, ' '));
Функция ref() возвращает объект, который содержит переданную ссылку, являясь при этом вполне копируемым. Существует также функция cref(), создающая класс, содержащий ссылку на константу. Подобно функции bind(), функции ref() и cref() определены в заголовке functional.
Совместимость с прежней версией: привязка аргументовПрежние версии языка С++ имели много больше ограничений, и все же более сложный набор средств привязки аргументов к функциям. Библиотека определяет две функции — bind1st() и bind2nd(). Подобно функции bind(), эти функции получают функцию и создают новый вызываемый объект для вызова переданной функции с одним из ее параметров, связанным с переданным значением. Но эти функции могут связать только первый или второй параметр соответственно. Поскольку они имеют очень много ограничений, в новом стандарте эти функции не рекомендованы. Это устаревшее средство, которое может не поддерживаться в будущих выпусках. Современные программы С++ должны использовать функцию bind().
Упражнения раздела 10.3.4Упражнение 10.22. Перепишите программу подсчета слов размером 6 символов с использованием функций вместо лямбда-выражений.
Упражнение 10.23. Сколько аргументов получает функции bind()?
Упражнение 10.24. Используйте функции bind() и check_size() для поиска первого элемента вектора целых чисел, значение которого больше длины заданного строкового значения.
Упражнение 10.25. В упражнениях раздела 10.3.2 была написана версия функции biggies(), использующая алгоритм partition(). Перепишите эту функцию так, чтобы использовать функции check_size() и bind().
10.4. Возвращаясь к итераторам
В дополнение к итераторам, определяемым для каждого из контейнеров, библиотека определяет в заголовке iterator несколько дополнительных видов итераторов.
• Итератор вставки (insert iterator). Связан с контейнером и применяется для вставки элементов в контейнер.
• Потоковый итератор (stream iterator). Может быть связан с потоком ввода или вывода и применяется для перебора связанного потока ввода- вывода.
• Реверсивный итератор (reverse iterator). Перемещается назад, а не вперед. У всех библиотечных контейнеров, кроме forward_list, есть реверсивные итераторы.
• Итератор перемещения (move iterator). Итератор специального назначения; он перемещает элементы, а не копирует. Эти итераторы рассматриваются в разделе 13.6.2.
10.4.1. Итераторы вставки
Адаптер вставки (inserter), или адаптер inserter, — это адаптер итератора (см. раздел 9.6), получающий контейнер и возвращающий итератор, позволяющий вставлять элементы в указанный контейнер. Присвоение значения при помощи итератора вставки приводит к вызову контейнерной функции, добавляющей элемент в определенную позицию заданного контейнера. Операторы, поддерживающие эти итераторы, приведены в табл. 10.2.
Таблица 10.2. Операторы итератора вставки
it = t Вставляет значение t в позицию, обозначенную итератором it. В зависимости от вида итератора вставки и с учетом того, что он связан с контейнером с, вызывает функции c.push_back(t), c.push_front(t) и c.insert(t, p), где p — позиция итератора, заданная адаптеру вставки *it, ++it, it++ Эти операторы существуют, но ничего не делают с итератором it. Каждый оператор возвращает итератор itСуществуют три вида адаптеров вставки, которые отличаются позицией добавляемых элементов.
• Адаптер back_inserter (см. раздел 10.2.2) создает итератор, использующий функцию push_back().
• Адаптер front_inserter создает итератор, использующий функцию push_front().
• Адаптер inserter создает итератор, использующий функцию insert(). Кроме имени контейнера, адаптеру inserter передают второй аргумент: итератор, указывающий позицию, перед которой должна начаться вставка.
Адаптер front_inserter можно использовать, только если у контейнера есть функция push_front(). Аналогично адаптер back_inserter можно использовать, только если у контейнера есть функция push_back().
Важно понимать, что при вызове адаптера inserter(с, iter) возвращается итератор, который при использовании вставляет элементы перед элементом, первоначально обозначенным итератором iter. Таким образом, если it — итератор, созданный адаптером inserter, то присвоение *it = val; ведет себя, как следующий код:
it = c.insert(it, val); // it указывает на недавно добавленный элемент
++it; // инкремент it, чтобы он указывал на тот же элемент,
// что и прежде
Итератор, созданный адаптером front_inserter, ведет себя прямо противоположно итератору, созданному адаптером inserter. При использовании адаптера front_inserter элементы всегда вставляются перед текущим первым элементом контейнера. Даже если переданная адаптеру inserter позиция первоначально обозначает первый элемент, то, как только будет вставлен элемент перед этим элементом, он больше не будет элементом в начале контейнера:
list<int> lst = {1,2,3,4};
list<int> lst2, lst3; // пустой список
// после завершения копирования, lst2 содержит 4 3 2 1
copy(lst.cbegin(), lst.cend(), front_inserter(lst2));
// после завершения копирования, lst3 содержит 1 2 3 4
copy(lst.cbegin(), lst.cend(), inserter(lst3, lst3.begin()));
Когда происходит вызов front_inserter(c), возвращается итератор вставки, который последовательно вызывает функцию push_front(). По мере вставки каждого элемента он становится новым первым элементом контейнера с. Следовательно, адаптер front_inserter возвращает итератор, который полностью изменяет порядок последовательности, в которую осуществляется вставка; адаптеры inserter и back_inserter так не поступают.
Упражнения раздела 10.4.1Упражнение 10.26. Объясните различия между тремя итераторами вставки.
Упражнение 10.27. В дополнение к функции unique() (см. раздел 10.2.3) библиотека определяет функцию unique_copy(), получающую третий итератор, обозначающий назначение копирования уникальных элементов. Напишите программу, которая использует функцию unique_copy() для копирования уникальных элементов вектора в первоначально пустой список.
Упражнение 10.28. Скопируйте вектор, содержащий значения от 1 до 9, в три других контейнера. Используйте адаптеры inserter, back_inserter и front_inserter соответственно для добавления элементов в эти контейнеры. Предскажите вид результирующей последовательности в зависимости от вида адаптера вставки и проверьте свои предсказания на написанной программе.