С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 98
Текст из файла (страница 98)
Итераторы вставкиВот еще один фрагмент программы, в котором есть тонкая, но серьезная ошибка. Видитеint ia[] = { 0, 1, 1, 2, 3, 5, 5, 8 };vector< int > ivec( ia, ia+8 ), vres;// ...// поведение программы во время выполнения не определеноли вы, в чем она заключается?unique_copy( ivec.begin(), ivec.end(), vres.begin() );566С++ для начинающих567Проблема вызвана тем, что алгоритм unique_copy() использует присваивание длякопирования значения каждого элемента из вектора ivec, но эта операция завершитсянеудачно, поскольку в vres не выделено место для хранения девяти целых чисел.Можно было бы написать две версии алгоритма unique_copy(): одна присваиваетэлементы, а вторая вставляет их. Эта последняя версия должна, в таком случае,поддерживать вставку в начало, в конец или в произвольное место контейнера.Альтернативный подход, принятый в стандартной библиотеке, заключаетсяопределении трех адаптеров, которые возвращают специальные итераторы вставки:•вback_inserter() вызывает определенную для контейнера операцию вставкиpush_back() вместо оператора присваивания.
Аргументом back_inserter()// правильно: теперь unique_copy() вставляет элементы с помощью// vres.push_back()...unique_copy( ivec.begin(), ivec.end(),является сам контейнер. Например, вызов unique_copy() можно исправить, написав:back_inserter( vres ) );•front_inserter() вызывает определенную для контейнера операцию вставкиpush_front() вместо оператора присваивания. Аргументом front_inserter() тожеявляется сам контейнер. Заметьте, однако, что класс vector не поддерживает// увы, ошибка:// класс vector не поддерживает операцию push_front()// следует использовать контейнеры deque или listunique_copy( ivec.begin(), ivec.end(),push_front(), так что использовать такой адаптер для вектора нельзя:front_inserter( vres ) );•inserter() вызывает определенную для контейнера операцию вставки insert()вместо оператора присваивания. inserter() принимает два аргумента: самunique_copy( ivec.begin(), ivec.end(),контейнер и итератор, указывающий позицию, с которой должна начаться вставка:inserter( vres ), vres.begin() );•Итератор, указывающий на позицию начала вставки, сдвигается вперед после каждойвставки, так что элементы располагаются в нужном порядке, как если бы мыvector< int >::iterator iter = vres.begin(),iter2 = ivec.begin();for ( ; iter2 != ivec.end() ++ iter, ++iter2 )написали:vres.insert( iter, *iter2 );С++ для начинающих12.4.2.
Обратные итераторыОперации begin() и end() возвращают соответственно итераторы, указывающие напервый элемент и на элемент, расположенный за последним. Можно также вернутьобратный итератор, обходящий контейнер от последнего элемента к первому. Во всехконтейнерах для поддержки такой возможности используются операции rbegin() иvector< int > vec0;const vector< int > vec1;vector< int >::reverse_iterator r_iter0 = vec0.rbegin();rend(). Есть константные и неконстантные версии обратных итераторов:vector< int >::const_reverse_iterator r_iter1 = vec1.rbegin();Обратный итератор применяется так же, как прямой. Разница состоит в реализацииоператоров перехода к следующему и предыдущему элементам.
Для прямого итератораоператор ++ дает доступ к следующему элементу контейнера, тогда как для обратного – к// обратный итератор обходит вектор от конца к началуvector< type >::reverse_iterator r_iter;for ( r_iter = vec0.rbegin();// r_iter указывает на последний элементr_iter != vec0.rend();// пока не достигли элемента перед первымr_iter++ )// переходим к предыдущему элементупредыдущему. Например, для обхода вектора в обратном направлении следует написать:{ /* ... */ }Инвертирование семантики операторов инкремента и декремента может внести путаницу,но зато позволяет программисту передавать алгоритму пару обратных итераторов вместопрямых. Так, для сортировки вектора в порядке убывания мы передаем алгоритму// сортирует вектор в порядке возрастанияsort( vec0.begin(), vec0.end() );// сортирует вектор в порядке убыванияsort() пару обратных итераторов:sort( vec0.rbegin(), vec0.rend() );12.4.3.
Потоковые итераторыСтандартная библиотека предоставляет средства для работы потоковых итераторовчтения и записи совместно со стандартными контейнерами и обобщенными алгоритмами.Класс istream_iterator поддерживает итераторные операции с классом istream илиодним из производных от него, например ifstream для работы с потоком ввода изфайла. Аналогично ostream_iterator поддерживает итераторные операции с классомostream или одним из производных от него, например ofstream для работы с потокомвывода в файл.
Для использования любого из этих итераторов следует включитьзаголовочный файл568С++ для начинающих569#include <iterator>В следующей программе мы пользуемся потоковым итератором чтения для получения изстандартного ввода последовательности целых чисел в вектор, а затем применяемпотоковый итератор записи в качестве целевого в обобщенном алгоритме#include#include#include#include#include<iostream><iterator><algorithm><vector><functional>/** вход:* 23 109 45 89 6 34 12 90 34 23 56 23 8 89 23** выход:* 109 90 89 56 45 34 23 12 8 6*/int main(){istream_iterator< int > input( cin );istream_iterator< int > end_of_stream;vector<int> vec;copy ( input, end_of_stream, inserter( vec, vec.begin() ));sort( vec.begin(), vec.end(), greater<int>() );ostream_iterator< int > output( cout, " " );unique_copy( vec.begin(), vec.end(), output );unique_copy():}12.4.4.
Итератор istream_iteratorВ общем виде объявление потокового итератора чтения istream_iterator имеет форму:istream_iterator<Type> identifier( istream& );1.1. Если имеющийся у Вас компилятор пока не поддерживает параметршаблонов по умолчанию, то конструктору istream_iterator необходимобудет явно передать также и второй аргумент: тип difference_type,способный хранить результат вычитания двух итераторов контейнера, кудапомещаются элементы.
Например, в разделе 12.2 при изучении программы,которая должна транслироваться компилятором, не поддерживающимпараметры шаблонов по умолчанию, мы писали:typedef vector<string,allocator>::difference_type diff_typeistream_iterator< string, diff_type > input_set1( infile1 ), eos;istream_iterator< string, diff_type > input_set2( infile2 );Примечание [O.A.3]: Нумерация сносок сбита.С++ для начинающихгде Type – это любой встроенный или пользовательский тип класса, для которогоопределен оператор ввода.
Аргументом конструктора может быть объект либо классаistream, например cin, либо производного от него класса с открытым типом#include#include#include#include<iterator><fstream><string><complex>// прочитать последовательность объектов типа complex// из стандартного вводаistream_iterator< complex > is_complex( cin );// прочитать последовательность строк из именованного файлаifstream infile( "C++Primer" );наследования – ifstream:istream_iterator< string > is_string( infile );При каждом применении оператора инкремента к объекту типа istream_iteratorчитается следующий элемент из входного потока, для чего используется операторoperator>>(). Чтобы сделать то же самое в обобщенных алгоритмах, необходимопредоставить пару итераторов, обозначающих начальную и конечную позицию в файле.Начальную позицию дает istream_iterator, инициализированный объектомistream, – такой, скажем, как is_string.
Для получения конечной позиции мы// конструирует итератор end_of_stream, который будет служить маркером// конца потока в итераторной пареistream_iterator< string > end_of_streamvector<string> text;// правильно: передаем пару итераторовcopy( is_string, end_of_stream,inserter( text, text.begin() ));используем специальный конструктор по умолчанию класса istream_iterator:12.4.5. Итератор ostream_iteratorОбъявление потокового итератора записи ostream_iterator может быть представлено вдвух формах:Если бы компилятор полностью удовлетворял стандарту C++, достаточно былобы написать так:istream_iterator< string > input_set1( infile1 ), eos;istream_iterator< string > input_set2( infile2 );570С++ для начинающихostream_iterator<Type> identifier( ostream& )ostream_iterator<Type> identifier( ostream&, char * delimiter )где Type – это любой встроенный или пользовательский тип класса, для которогоопределен оператор вывода (operator<<).
Во второй форме delimiter – эторазделитель, то есть C-строка символов, которая выводится в файл после каждогоэлемента. Такая строка должна заканчиваться двоичным нулем, иначе поведениепрограммы не определено (скорее всего, она аварийно завершит выполнение). В качествеаргумента ostream может выступать объект класса ostream, например cout, либо#include#include#include#include<iterator><fstream><string><complex>// записать последовательность объектов типа complex// в стандартный вывод, разделяя элементы пробеламиostream_iterator< complex > os_complex( cin, " " );// записать последовательность строк в именованный файлofstream outfile( "dictionary" );производного от него класса с открытым типом наследования, скажем ofstream:ostream_iterator< string > os_string( outfile, "\n" );Вот простой пример чтения из стандартного ввода и копирования на стандартный вывод#include <iterator>#include <algorithm>#include <iostream>int main(){copy( istream_iterator< int >( cin ),istream_iterator< int >(),ostream_iterator< int >( cout, " " ));с помощью безымянных потоковых итераторов и обобщенного алгоритма copy():}Ниже приведена небольшая программа, которая открывает указанный пользователемфайл и копирует его на стандартный вывод, применяя для этого алгоритм copy() ипотоковый итератор записи ostream_iterator:571С++ для начинающих#include#include#include#include<string><algorithm><fstream><iterator>main(){string file_name;cout << "please enter a file to open: ";cin >> file_name;if ( file_name.empty() || !cin ) {cerr << "unable to read file name\n"; return -1;}ifstream infile( file_name.c_str());if ( !infile ) {cerr << "unable to open " << file_name << endl;return -2;}istream_iterator< string > ins( infile ), eos;ostream_iterator< string > outs( cout, " " );copy( ins, eos, outs );}12.4.6.