Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Языки программирования / Литература по C++ / С.Липпман.С++ для начинающих.pdf
Скачиваний:
143
Добавлен:
31.05.2015
Размер:
5.55 Mб
Скачать

С++ для начинающих

577

#include <iterator>

В следующей программе мы пользуемся потоковым итератором чтения для получения из стандартного ввода последовательности целых чисел в вектор, а затем применяем потоковый итератор записи в качестве целевого в обобщенном алгоритме

#include <iostream> #include <iterator> #include <algorithm> #include <vector> #include <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_type istream_iterator< string, diff_type > input_set1( infile1 ), eos; istream_iterator< string, diff_type > input_set2( infile2 );

С++ для начинающих

578

где Type – это любой встроенный или пользовательский тип класса, для которого определен оператор ввода. Аргументом конструктора может быть объект либо класса istream, например cin, либо производного от него класса с открытым типом

#include <iterator> #include <fstream> #include <string> #include <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_stream

vector<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 );

С++ для начинающих

579

ostream_iterator<Type> identifier( ostream& )

ostream_iterator<Type> identifier( ostream&, char * delimiter )

где Type – это любой встроенный или пользовательский тип класса, для которого определен оператор вывода (operator<<). Во второй форме delimiter – это разделитель, то есть C-строка символов, которая выводится в файл после каждого элемента. Такая строка должна заканчиваться двоичным нулем, иначе поведение программы не определено (скорее всего, она аварийно завершит выполнение). В качестве аргумента ostream может выступать объект класса ostream, например cout, либо

#include <iterator> #include <fstream> #include <string> #include <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:

С++ для начинающих

580

#include <string> #include <algorithm> #include <fstream> #include <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. Пять категорий итераторов

Для поддержки полного набора обобщенных алгоритмов стандартная библиотека определяет пять категорий итераторов, положив в основу классификации множество операций. Это итераторы чтения (InputIterator), записи (OutputIterator), однонаправленные (ForwardIterator) и двунаправленные итераторы (BidirectionalIterator), а также итераторы с произвольным доступом (RandomAccessIterators). Ниже приводится краткое обсуждение характеристик каждой категории:

итератор чтения можно использовать для получения элементов из контейнера, но поддержка записи в контейнер не гарантируется. Такой итератор должен обеспечивать следующие операции (итераторы, поддерживающие также дополнительные операции, можно употреблять в качестве итераторов чтения при условии, что они удовлетворяют минимальным требованиям): сравнение двух итераторов на равенство и неравенство, префиксная и постфиксная форма инкремента итератора для адресации следующего элемента (оператор ++), чтение элемента с помощью оператора разыменования (*). Такого уровня поддержки требуют, в частности, алгоритмы find(), accumulate() и equal(). Любому алгоритму, которому необходим итератор чтения, можно передавать также и итераторы категорий, описанных в пунктах 3, 4 и 5;

итератор записи можно представлять себе как противоположный по функциональности итератору чтения. Иными словами, его можно использовать для записи элементов контейнера, но поддержка чтения из контейнера не гарантируется. Такие итераторы обычно применяются в качестве третьего аргумента алгоритма (например, copy()) и указывают на позицию, с которой надо начинать копировать.

С++ для начинающих

581

Любому алгоритму, которому необходим итератор записи, можно передавать также и итераторы других категорий, перечисленных в пунктах 3, 4 и 5;

однонаправленный итератор можно использовать для чтения и записи в контейнер, но только в одном направлении обхода (обход в обоих направлениях поддерживается итераторами следующей категории). К числу обобщенных алгоритмов, требующих как минимум однонаправленного итератора, относятся adjacent_find(), swap_range() и replace(). Конечно, любому алгоритму, которому необходим подобный итератор, можно передавать также и итераторы описанных ниже категорий;

двунаправленный итератор может читать и записывать в контейнер, а также перемещаться по нему в обоих направлениях. Среди обобщенных алгоритмов, требующих как минимум двунаправленного итератора, выделяются place_merge(), next_permutation() и reverse();

итератор с произвольным доступом, помимо всей функциональности, поддерживаемой двунаправленным итератором, обеспечивает доступ к любой позиции внутри контейнера за постоянное время. Подобные итераторы требуются таким обобщенным алгоритмам, как binary_search(), sort_heap() и nth-

element().

Упражнение 12.6 Объясните, почему некорректны следующие примеры. Какие ошибки обнаруживаются во

(a)const vector<string> file_names( sa, sa+6 ); vector<string>::iterator it = file_names.begin()+2;

(b)const vector<int> ivec;

fill( ivec.begin(), ivec.end(), ival );

(c)sort( ivec.begin(), ivec.end() );

(d)list<int> ilist( ia, ia+6 );

binary_search( ilist.begin(), ilist.end() );

время компиляции?

(e) sort( ivec1.begin(), ivec3.end() );

Упражнение 12.7

Напишите программу, которая читает последовательность целых чисел из стандартного ввода с помощью потокового итератора чтения istream_iterator. Нечетные числа поместите в один файл посредством ostream_iterator, разделяя значения пробелом. Четные числа таким же образом запишите в другой файл, при этом каждое значение должно размещаться в отдельной строке.

12.5. Обобщенные алгоритмы

Первые два аргумента любого обобщенного алгоритма (разумеется, есть исключения, которые только подтверждают правило) – это пара итераторов, обычно называемых first и last, ограничивающих диапазон элементов внутри контейнера или встроенного массива, к которым применяется этот алгоритм. Как правило, диапазон элементов

С++ для начинающих

582

(иногда его называют интервалом с включенной левой границей) обозначается

//читается так: включает первый и все последующие элементы,

//кроме последнего

следующим образом:

[ first, last )

Эта запись говорит о том, что диапазон начинается с элемента first и продолжается до элемента last, исключая последний. Если

first == last

то говорят, что диапазон пуст.

К паре итераторов предъявляется следующее требование: если начать с элемента first и последовательно применять оператор инкремента, то возможно достичь элемента last. Однако компилятор не в состоянии проверить выполнение этого ограничения; если оно нарушается, поведение программы не определено, обычно все заканчивается аварийным остановом и дампом памяти.

В объявлении каждого алгоритма указывается минимально необходимая категория итератора (см. раздел 12.4). Например, для алгоритма find(), реализующего однопроходный обход контейнера с доступом только для чтения, требуется итератор чтения, но можно передать и однонаправленный или двунаправленный итератор, а также итератор с произвольным доступом. Однако передача итератора записи приведет к ошибке. Не гарантируется, что ошибки, связанные с передачей итератора не той категории, будут обнаружены во время компиляции, поскольку категории итераторов – это не собственно типы, а лишь параметры-типы, передаваемые шаблону функции.

Некоторые алгоритмы существуют в нескольких версиях: в одной используется встроенный оператор, а во второй – объект-функция или указатель на функцию, которая предоставляет альтернативную реализацию оператора. Например, unique() по умолчанию сравнивает два соседних элемента с помощью оператора равенства, определенного для типа объектов в контейнере. Но если такой оператор равенства не определен или мы хотим сравнивать элементы иным способом, то можно передать либо объект-функцию, либо указатель на функцию, обеспечивающую нужную семантику. Встречаются также алгоритмы с похожими, но разными именами. Так, предикатные версии всегда имеют имя, оканчивающееся на _if, например find_if(). Скажем, есть алгоритм replace(), реализованный с помощью встроенного оператора равенства, и replace_if(), которому передается объект-предикат или указатель на функцию.

Алгоритмы, модифицирующие контейнер, к которому они применяются, обычно имеют две версии: одна преобразует содержимое контейнера по месту, а вторая возвращает копию исходного контейнера, в которой и отражены все изменения. Например, есть алгоритмы replace() и replace_copy() (имя версии с копированием всегда заканчивается на _copy). Однако не у всех алгоритмов, модифицирующих контейнер, имеется такая версия. К примеру, ее нет у алгоритма sort(). Если же мы хотим, чтобы сортировалась копия, то создать и передать ее придется самостоятельно.

Для использования любого обобщенного алгоритма необходимо включить в программу заголовочный файл

#include <algorithm>