Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
Упражнение 10.28. Скопируйте вектор, содержащий значения от 1 до 9, в три других контейнера. Используйте адаптеры inserter, back_inserter и front_inserter соответственно для добавления элементов в эти контейнеры. Предскажите вид результирующей последовательности в зависимости от вида адаптера вставки и проверьте свои предсказания на написанной программе.
10.4.2. Потоковые итераторы
Хотя типы iostream не относятся к контейнерам, есть итераторы, применимые к объектам типов ввода-вывода (см. раздел 8.1). Итератор istream_iterator (табл. 10.3) читает входной поток, а итератор ostream_iterator (табл. 10.4) пишет в поток вывода. Эти итераторы рассматривают свой поток как последовательность элементов определенного типа. Используя потоковый итератор, можно применять обобщенные алгоритмы для чтения или записи данных в объекты потоков.
Таблица 10.3. Операторы итератора istream_iterator
istream_iterator<T> in(is); in читает значения типа T из входного потока is istream_iterator<T> end; Итератор после конца для итератора istream_iterator, читающего значения типа Т in1 == in2 in1 != in2 in1 и in2 должны читать одинаковый тип. Они равны, если оба они конечные или оба связаны с тем же входным потоком *in Возвращает значение, прочитанное из потока in->mem Синоним для (*in).mem ++in, in++ Читает следующее значение из входного потока, используя оператор >> для типа элемента. Как обычно, префиксная версия возвращает ссылку на итератор после инкремента. Постфиксная версия возвращает прежнее значение Использование итератора istream_iteratorКогда создается потоковый итератор, необходимо определить тип объектов, которые итератор будет читать или записывать. Итератор istream_iterator использует оператор >> для чтения из потока. Поэтому тип, читаемый итератором istream_iterator, должен определять оператор ввода. При создании итератор istream_iterator следует связать с потоком. В качестве альтернативы итератор можно инициализировать значением по умолчанию. В результате будет создан итератор, который можно использовать как значение после конца.
istream_iterator<int> int_it(cin); // читает целые числа из cin
istream_iterator<int> int_eof; // конечное значение итератора
ifstream in("afile");
istream_iterator<string> str_it(in); // читает строки из "afile"
Для примера используем итератор istream_iterator для чтения со стандартного устройства ввода в вектор:
istream_iterator<int> in_iter(cin); // читает целые числа из cin
istream_iterator<int> eof; // "конечный" итератор istream
while (in_iter != eof) // пока есть что читать
// постфиксный инкремент читает поток и возвращает прежнее значение
// итератора. Обращение к значению этого итератора предоставляет
// предыдущее значение, прочитанное из потока
vec.push_back(*in_iter++);
Этот цикл читает целые числа из потока cin, сохраняя прочитанное в вектор vec. На каждой итерации цикл проверяет, не совпадает ли итератор in_iter со значением eof. Этот итератор был определен как пустой итератор istream_iterator, который используется как конечный итератор. Связанный с потоком итератор равен конечному итератору, только если связанный с ним поток достиг конца файла или произошла ошибка ввода-вывода.
Самая трудная часть этой программы — аргумент функции push_back(), который использует обращение к значению и постфиксные операторы инкремента. Это выражение работает точно так же, как и другие выражения, совмещающие обращение к значению с постфиксным инкрементом (см. раздел 4.5). Постфиксный инкремент переводит поток на чтение следующего значения, но возвращает прежнее значение итератора. Это прежнее значение содержит прежнее значение, прочитанное из потока. Для того чтобы получить это значение, осуществляется обращение к значению этого итератора.
Особенно полезно то, что эту программу можно переписать так:
istream_iterator<int> in_iter(cin), eof; // читает целые числа из cin
vector<int> vec(in_iter, eof); // создает вектор vec из
// диапазона итераторов
Здесь вектор vec создается из пары итераторов, которые обозначают диапазон элементов. Это итераторы istream_iterator, следовательно, диапазон получается при чтении связанного потока. Этот конструктор читает поток cin, пока он не встретит конец файла, или ввод, тип которого отличается от int. Прочитанные элементы используются для создания вектора vec.
Использование потоковых итераторов с алгоритмамиПоскольку алгоритмы используют функции итераторов, а потоковые итераторы поддерживают по крайней мере некоторые функции итератора, потоковые итераторы можно использовать с некоторыми из алгоритмов. Какие именно алгоритмы применимы с потоковыми итераторами, рассматривается в разделе 10.5.1. В качестве примера рассмотрим вызов функции accumulate() с парой итераторов istream_iterators:
istream_iterator<int> in (cin), eof;
cout << accumulate(in, eof, 0) << endl;
Этот вызов создаст сумму значений, прочитанных со стандартного устройства ввода. Если ввод в этой программе будет таким:
23 109 45 89 6 34 12 90 34 23 56 23 8 89 23
то результат будет 664.
Итераторы istream_iterator позволяют использовать ленивое вычислениеТо, что итератор istream_iterator связан с потоком, еще не гарантирует, что он начнет читать поток немедленно. Некоторые реализации разрешают задержать чтение потока, пока итератор не будет использован. Гарантированно, что поток будет прочитан перед первым обращением к значению итератора. Для большинства программ не имеет никакого значения, будет ли чтение немедленным или отсроченным. Но если создается итератор istream_iterator, который удаляется без использования, или если необходима синхронизация чтения того же потока из двух разных объектов, то тогда придется позаботиться и о моменте чтения.
Использование итератора ostream_iteratorИтератор ostream_iterator может быть определен для любого типа, у которого есть оператор вывода (оператор <<). При создании итератора ostream_iterator можно (необязательно) предоставить второй аргумент, определяющий символьную строку, выводимую после каждого элемента. Это должна быть символьная строка в стиле С (т.е. строковый литерал или указатель на массив с нулевым символом в конце). Итератор ostream_iterator следует связать с определенным потоком. Не бывает пустого итератора ostream_iterator или такового после конца.
Таблица 10.4. Операторы итератора ostream_iterator
ostream iterator<T> out(os); out пишет значения типа T в поток вывода os ostream_iterator<T> out(os, d); out пишет значения типа T, сопровождаемые d в поток вывода os. d указывает на символьный массив с нулевым символом в конце out = val Записывает val в поток вывода, с которым связан out, используя оператор <<. Тип val должен быть совместим с типом, который можно писать в out *out, ++out, out++ Эти операторы существуют, но ничего не делают с out. Каждый оператор возвращает итератор outИтератор ostream_iterator можно использовать для записи последовательности значений:
ostream_iterator<int> out_iter(cout, " ");
for (auto e : vec)
*out_iter++ = e; // это присвоение запишет элемент в cout
cout << endl;
Эта программа запишет каждый элемент вектора vec в поток cout, сопровождая каждый элемент пробелом. При каждом присвоении значения итератора out_iter происходит запись.