При написании собственного алгоритма для работы с диапазонами (т.е. со стандартными контейнерами) вы должны работать с аргументами-итераторами, а не с аргументами-контейнерами. У вас может возникнуть желание объявить copyIf так, чтобы он принимал два контейнера, а не итератор исходного и результирующего диапазонов, но это менее обобщенное решение, чем диапазоны. Во-первых, если передавать аргументы-контейнеры, то станет невозможно работать с подмножеством элементов контейнера. Далее, в теле copyIf появится зависимость от методов контейнеров begin и end, которые дадут требуемый диапазон, и возвращаемый тип будет зависеть от типа контейнера, используемого в качестве выходного. Это означает, что использование в copyIf нестандартных диапазонов, таких как встроенные массивы или собственные контейнеры, работать не будет. Именно по этим и некоторым другим причинам все стандартные алгоритмы оперируют с диапазонами.
Наконец, если вы пишете свой алгоритм, дважды убедитесь, что стандартные алгоритмы вас не устраивают. На первый взгляд они могут казаться очень простыми алгоритмами, но их кажущаяся простота проистекает из их обобщенности, и в девяти случаях из десяти их можно расширить так, что они подойдут для новых задач. Иногда следует стремиться к повторному использованию стандартных алгоритмов, так как это дает гарантию переносимости и эффективности.
Рецепт 7.5.
7.11. Печать диапазона в поток
Имеется диапазон элементов, который требуется напечатать в поток, например, в cout с целью отладки.
Напишите шаблон функции, который принимает диапазон или контейнер, перебирает все его элементы и использует алгоритм сору и ostream_iterator для записи. Если требуется дополнительное форматирование, напишите свой простой алгоритм, который перебирает диапазон и печатает каждый элемент в поток. (См. пример 7.11)
#include
#include
#include
#include
#include
using namespace std;
int main() {
// Итератор ввода - это противоположность итератору вывода: он
// читает элементы из потока так. как будто это контейнер.
cout << "Введите несколько строк: ";
istream_iterator
istream_iterator
vector
// Используем выходной поток как контейнер, используя
// output_iterator. Он создает итератор вывода, для которого запись
// в каждый элемент эквивалентна записи в поток.
copy(v.begin(), v.end(), ostreamIterator
}
Вывод примера 7.11 может выглядеть так.
Введите несколько строк: z x y a b с
^Z
z, x, y, a, b, с,
Потоковый итератор — это итератор, который основан на потоке, а не на диапазоне элементов контейнера, и позволяет рассматривать поток как итератор ввода (читать из разыменованного значения и увеличивать итератор) или итератор вывода (аналогично итератору ввода, но для записи в разыменованное значение вместо чтения из него). Это облегчает чтение значений (особенно строк) из потока, что делается в нескольких других примерах этой главы, и запись значений в поток, что делается в примере 7.11. Я знаю, что этот рецепт посвящен записи диапазона в поток, но позвольте мне немного отойти от этой задачи и, поскольку я использую потоковые итераторы во многих примерах этой главы, объяснить, что это такое.
В примере 7.11 показаны три ключевые части istream_iterator. Первая часть — это создание istream_iterator, указывающего на начало потокового ввода. Это делается вот так.
istream_iterator
В результате создается итератор с именем start, который указывает на первый элемент входной последовательности, точно так же, как vec.begin (vec — это vector) возвращает итератор, который указывает на первый элемент в векторе. Аргумент шаблона string говорит istream_iterator, что элементы в этой последовательности имеют тип string. Аргумент конструктора cin — это входной поток, из которого производится чтение. Однако это абстракция, так как первого элемента не существует, поскольку из cin еще ничего прочитано не было. Это произойдет несколько позже.
Вторая часть итератора входного потока — это маркер конца, который создается вот так.
istream_iterator