Главная страница
Навигация по странице:

  • 12.3.6. Реализация объекта-функции

  • 12.4.1. Итераторы вставки

  • 12.4.3. Потоковые итераторы

  • 12.4.4. Итератор istream_iterator

  • Примечание [O.A.3]

  • 12.4.5. Итератор ostream_iterator

  • Язык программирования C++. Вводный курс. С для начинающих


    Скачать 5.41 Mb.
    НазваниеС для начинающих
    Дата24.08.2022
    Размер5.41 Mb.
    Формат файлаpdf
    Имя файлаЯзык программирования C++. Вводный курс.pdf
    ТипДокументы
    #652350
    страница54 из 93
    1   ...   50   51   52   53   54   55   56   57   ...   93
    563
    12.3.5.
    Адаптеры функций для объектов-функций
    В стандартной библиотеке имеется также ряд адаптеров функций, предназначенных для специализации и расширения как унарных, так и бинарных объектов-функций.
    Адаптеры – это специальные классы, разбитые на следующие две категории:

    связыватели (binders). Это адаптеры, преобразующие бинарный объект-функцию в унарный объект, связывая один из аргументов с конкретным значением. Например, для подсчета в контейнере всех элементов, которые меньше или равны 10, следует передать алгоритму count_if() объект-функцию less_equal, один из аргументов которого равен 10. В следующем разделе мы покажем, как это сделать;

    отрицатели (negators). Это адаптеры, изменяющие значение истинности объекта- функции на противоположное. Например, для подсчета всех элементов внутри контейнера, которые больше 10, мы могли бы передать алгоритму count_if() отрицатель объекта-функции less_equal, один из аргументов которого равен 10.
    Конечно, в данном случае проще передать связыватель объекта-функции greater, ограничив один из аргументов со значением 10.
    В стандартную библиотеку входит два предопределенных адаптера-связывателя: bind1st и bind2nd, причем bind1st связывает некоторое значение с первым аргументом бинарного объекта-функции, а bind2nd – со вторым. Например, для подсчета внутри контейнера всех элементов, которые меньше или равны 10, мы могли бы передать алгоритму count_if() следующее: bind2nd( less_equal(), 10 ));
    В стандартной библиотеке также есть два предопределенных адаптера-отрицателя: not1 и not2
    . not1 инвертирует значение истинности унарного предиката, являющегося объектом-функцией, а not2 – значение бинарного предиката. Для отрицания рассмотренного выше связывателя объекта-функции less_equal можно написать следующее: not1( bind2nd( less_equal(), 10 )));
    Другие примеры использования связывателей и отрицателей приведены в Приложении, вместе с примерами использования каждого алгоритма.
    12.3.6.
    Реализация объекта-функции
    При реализации программы в разделе 12.2 нам уже приходилось определять ряд объектов-функций. В этом разделе мы изучим необходимые шаги и возможные вариации при определении класса объекта-функции. (В главе 13 определение класса рассматривается детально; в главе 15 обсуждается перегрузка операторов.) count_if( vec.begin(), vec.end(), count_if( vec.begin(), vec.end(),

    С++ для начинающих
    564
    В самой простой форме определение класса объекта-функции сводится к перегрузке оператора вызова. Вот, например, унарный объект-функция, определяющий, что некоторое значение меньше или равно 10:
    };
    Теперь такой объект-функцию можно использовать точно так же, как предопределенный.
    Вызов алгоритма count_if() с помощью нашего объекта-функции выглядит следующим образом: count_if( vec.begin(), vec.end(), less_equal_ten() );
    Разумеется, возможности этого класса весьма ограничены. Попробуем применить отрицатель, чтобы подсчитать, сколько в контейнере элементов, больших 10: not1(less_equal_then ())); или обобщить реализацию, разрешив пользователю задавать значение, с которым надо сравнивать каждый элемент контейнера. Для этого достаточно ввести в класс член для хранения такого значения и реализовать конструктор, инициализирующий данный член указанной пользователем величиной:
    };
    Новый объект-функция применяется для задания произвольного целого значения.
    Например, при следующем вызове подсчитывается число элементов, меньших или равных 25: count_if( vec.begin(), vec.end(), less_equal_value( 25 ));
    Разрешается реализовать класс и без конструктора, если параметризовать его значением, с которым производится сравнение:
    // простейшая форма класса объекта-функции class less_equal_ten { public: bool operator() ( int val )
    { return val <= 10; } count_if( vec.begin(), vec.end(), class less_equal_value { public: less_equal_value( int val ) : _val( val ) {} bool operator() ( int val ) { return val <= _val; } private: int _val;

    С++ для начинающих
    565
    };
    Вот как надо было бы вызвать такой класс для подсчета числа элементов, меньших или равных 25: count_if( vec.begin(), vec.end(), less_equal_value<25>());
    (Другие примеры определения собственных объектов-функций можно найти в
    Приложении.)
    Упражнение 12.4
    Используя предопределенные объекты-функции и адаптеры, создайте объекты-функции для решения следующих задач:
    (a) Найти все значения, большие или равные 1024.
    (b) Найти все строки, не равные "pooh".
    (c) Умножить все значения на 2.
    Упражнение 12.5
    Определите объект-функцию для возврата среднего из трех объектов. Определите функцию для выполнения той же операции. Приведите примеры использования каждого объекта непосредственно и путем передачи его функции. Покажите, в чем сходство и различие этих решений.
    12.4.
    Еще раз об итераторах
    Следующая реализация шаблона функции не компилируется. Можете ли вы сказать, почему?
    }
    Проблема в том, что у ссылки vec есть спецификатор const, а мы пытаемся связать с ней итератор без такого спецификатора. Если бы это было разрешено, то ничто не помешало бы нам модифицировать с помощью этого итератора элементы вектора. Для template < int _val > class less_equal_value { public: bool operator() ( int val ) { return val <= _val; }
    // в таком виде это не компилируется template < typename type > int count( const vector< type > &vec, type value )
    { int count = 0; vector< type >::iterator iter = vec.begin(); while ( iter != vec.end() ) if ( *iter == value )
    ++count; return count;

    С++ для начинающих
    566
    предотвращения подобной ситуации язык требует, чтобы итератор, связанный с const- вектором, был константным. Мы можем сделать это следующим образом: vector< type>::const_iterator iter = vec.begin();
    Требование, чтобы с const-контейнером был связан только константный итератор, аналогично требованию о том, чтобы const-массив адресовался только константным указателем. В обоих случаях это вызвано необходимостью гарантировать, что содержимое const-контейнера не будет изменено.
    Операции begin() и end() перегружены и возвращают константный или неконстантный итератор в зависимости от наличия спецификатора const в объявлении контейнера. Если дана такая пара объявлений: const vector< int > vec1; то при обращениях к begin() и end() для vec0 будет возвращен неконстантный, а для vec1
    – константный итератор: vector< int >::const_iterator iter1 = vec1.begin();
    Разумеется, присваивание константному итератору неконстантного разрешено всегда.
    Например: vector< int >::const_iterator iter2 = vec0.begin();
    12.4.1.
    Итераторы вставки
    Вот еще один фрагмент программы, в котором есть тонкая, но серьезная ошибка. Видите ли вы, в чем она заключается? unique_copy( ivec.begin(), ivec.end(), vres.begin() );
    // правильно: это компилируется без ошибок vector< int > vec0; vector< int >::iterator iter0 = vec0.begin();
    // правильно: инициализация константного итератора неконстантным int ia[] = { 0, 1, 1, 2, 3, 5, 5, 8 }; vector< int > ivec( ia, ia+8 ), vres;
    // ...
    // поведение программы во время выполнения не определено

    С++ для начинающих
    567
    Проблема вызвана тем, что алгоритм unique_copy() использует присваивание для копирования значения каждого элемента из вектора ivec, но эта операция завершится неудачно, поскольку в vres не выделено место для хранения девяти целых чисел.
    Можно было бы написать две версии алгоритма unique_copy(): одна присваивает элементы, а вторая вставляет их. Эта последняя версия должна, в таком случае, поддерживать вставку в начало, в конец или в произвольное место контейнера.
    Альтернативный подход, принятый в стандартной библиотеке, заключается в определении трех адаптеров, которые возвращают специальные итераторы вставки:

    back_inserter()
    вызывает определенную для контейнера операцию вставки push_back()
    вместо оператора присваивания. Аргументом back_inserter() является сам контейнер. Например, вызов unique_copy() можно исправить, написав: back_inserter( vres ) );

    front_inserter()
    вызывает определенную для контейнера операцию вставки push_front()
    вместо оператора присваивания. Аргументом front_inserter() тоже является сам контейнер. Заметьте, однако, что класс vector не поддерживает push_front()
    , так что использовать такой адаптер для вектора нельзя: front_inserter( vres ) );

    inserter()
    вызывает определенную для контейнера операцию вставки insert() вместо оператора присваивания. inserter() принимает два аргумента: сам контейнер и итератор, указывающий позицию, с которой должна начаться вставка: inserter( vres ), vres.begin() );

    Итератор, указывающий на позицию начала вставки, сдвигается вперед после каждой вставки, так что элементы располагаются в нужном порядке, как если бы мы написали: vres.insert( iter, *iter2 );
    // правильно: теперь unique_copy() вставляет элементы с помощью
    // vres.push_back()... unique_copy( ivec.begin(), ivec.end(),
    // увы, ошибка:
    // класс vector не поддерживает операцию push_front()
    // следует использовать контейнеры deque или list unique_copy( ivec.begin(), ivec.end(), unique_copy( ivec.begin(), ivec.end(), vector< int >::iterator iter = vres.begin(), iter2 = ivec.begin(); for ( ; iter2 != ivec.end() ++ iter, ++iter2 )

    С++ для начинающих
    568
    12.4.2.
    Обратные итераторы
    Операции begin() и end() возвращают соответственно итераторы, указывающие на первый элемент и на элемент, расположенный за последним. Можно также вернуть обратный итератор, обходящий контейнер от последнего элемента к первому. Во всех контейнерах для поддержки такой возможности используются операции rbegin() и rend()
    . Есть константные и неконстантные версии обратных итераторов: vector< int >::const_reverse_iterator r_iter1 = vec1.rbegin();
    Обратный итератор применяется так же, как прямой. Разница состоит в реализации операторов перехода к следующему и предыдущему элементам. Для прямого итератора оператор ++ дает доступ к следующему элементу контейнера, тогда как для обратного – к предыдущему. Например, для обхода вектора в обратном направлении следует написать:
    { /* ... */ }
    Инвертирование семантики операторов инкремента и декремента может внести путаницу, но зато позволяет программисту передавать алгоритму пару обратных итераторов вместо прямых. Так, для сортировки вектора в порядке убывания мы передаем алгоритму sort()
    пару обратных итераторов: sort( vec0.rbegin(), vec0.rend() );
    12.4.3.
    Потоковые итераторы
    Стандартная библиотека предоставляет средства для работы потоковых итераторов чтения и записи совместно со стандартными контейнерами и обобщенными алгоритмами.
    Класс istream_iterator поддерживает итераторные операции с классом istream или одним из производных от него, например ifstream для работы с потоком ввода из файла. Аналогично ostream_iterator поддерживает итераторные операции с классом ostream или одним из производных от него, например ofstream для работы с потоком вывода в файл. Для использования любого из этих итераторов следует включить заголовочный файл vector< int > vec0; const vector< int > vec1; vector< int >::reverse_iterator r_iter0 = vec0.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() );
    // сортирует вектор в порядке убывания

    С++ для начинающих
    569
    #include
    В следующей программе мы пользуемся потоковым итератором чтения для получения из стандартного ввода последовательности целых чисел в вектор, а затем применяем потоковый итератор записи в качестве целевого в обобщенном алгоритме unique_copy()
    :
    }
    12.4.4.
    Итератор istream_iterator
    В общем виде объявление потокового итератора чтения istream_iterator имеет форму: istream_iterator identifier( istream& );1.
    1.
    Если имеющийся у Вас компилятор пока не поддерживает параметр шаблонов по умолчанию, то конструктору istream_iterator необходимо будет явно передать также и второй аргумент: тип difference_type, способный хранить результат вычитания двух итераторов контейнера, куда помещаются элементы. Например, в разделе 12.2 при изучении программы, которая должна транслироваться компилятором, не поддерживающим параметры шаблонов по умолчанию, мы писали: typedef vector::difference_type diff_type istream_iterator< string, diff_type > input_set1( infile1 ), eos; istream_iterator< string, diff_type > input_set2( infile2 );
    #include
    #include
    #include
    #include
    #include
    /*
    * вход:
    * 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 vec; copy ( input, end_of_stream, inserter( vec, vec.begin() )); sort( vec.begin(), vec.end(), greater() ); ostream_iterator< int > output( cout, " " ); unique_copy( vec.begin(), vec.end(), output );
    Примечание [O.A.3]: Нумера ция сносок сбита.

    С++ для начинающих
    570
    где Type – это любой встроенный или пользовательский тип класса, для которого определен оператор ввода. Аргументом конструктора может быть объект либо класса istream
    , например cin, либо производного от него класса с открытым типом наследования – ifstream: istream_iterator< string > is_string( infile );
    При каждом применении оператора инкремента к объекту типа istream_iterator читается следующий элемент из входного потока, для чего используется оператор operator>>()
    . Чтобы сделать то же самое в обобщенных алгоритмах, необходимо предоставить пару итераторов, обозначающих начальную и конечную позицию в файле.
    Начальную позицию дает istream_iterator, инициализированный объектом istream
    , – такой, скажем, как is_string. Для получения конечной позиции мы используем специальный конструктор по умолчанию класса istream_iterator:
    12.4.5.
    Итератор ostream_iterator
    Объявление потокового итератора записи ostream_iterator может быть представлено в двух формах:
    Если бы компилятор полностью удовлетворял стандарту C++, достаточно было бы написать так: istream_iterator< string > input_set1( infile1 ), eos; istream_iterator< string > input_set2( infile2 );
    #include
    #include
    #include
    #include
    // прочитать последовательность объектов типа complex
    // из стандартного ввода istream_iterator< complex > is_complex( cin );
    // прочитать последовательность строк из именованного файла ifstream infile( "C++Primer" );
    // конструирует итератор end_of_stream, который будет служить маркером
    // конца потока в итераторной паре istream_iterator< string > end_of_stream vector text;
    // правильно: передаем пару итераторов copy( is_string, end_of_stream, inserter( text, text.begin() ));

    С++ для начинающих
    571
    ostream_iterator identifier( ostream&, char * delimiter ) где Type – это любой встроенный или пользовательский тип класса, для которого определен оператор вывода (operator<<). Во второй форме delimiter – это разделитель, то есть C-строка символов, которая выводится в файл после каждого элемента. Такая строка должна заканчиваться двоичным нулем, иначе поведение программы не определено (скорее всего, она аварийно завершит выполнение). В качестве аргумента ostream может выступать объект класса ostream, например cout, либо производного от него класса с открытым типом наследования, скажем ofstream: ostream_iterator< string > os_string( outfile, "\n" );
    Вот простой пример чтения из стандартного ввода и копирования на стандартный вывод с помощью безымянных потоковых итераторов и обобщенного алгоритма copy():
    }
    Ниже приведена небольшая программа, которая открывает указанный пользователем файл и копирует его на стандартный вывод, применяя для этого алгоритм copy() и потоковый итератор записи ostream_iterator: ostream_iterator identifier( ostream& )
    #include
    #include
    #include
    #include
    // записать последовательность объектов типа complex
    // в стандартный вывод, разделяя элементы пробелами ostream_iterator< complex > os_complex( cin, " " );
    // записать последовательность строк в именованный файл ofstream outfile( "dictionary" );
    #include
    #include
    #include int main()
    { copy( istream_iterator< int >( cin ), istream_iterator< int >(), ostream_iterator< int >( cout, " " ));

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

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

    итератор записи можно представлять себе как противоположный по функциональности итератору чтения. Иными словами, его можно использовать для записи элементов контейнера, но поддержка чтения из контейнера не гарантируется.
    Такие итераторы обычно применяются в качестве третьего аргумента алгоритма
    (например, copy()) и указывают на позицию, с которой надо начинать копировать.
    #include
    #include
    #include
    #include 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 );

    С++ для начинающих
    1   ...   50   51   52   53   54   55   56   57   ...   93


    написать администратору сайта