Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


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



При реализации программы в разделе 12.2 нам уже приходилось определять ряд объектов-функций. В этом разделе мы изучим необходимые шаги и возможные вариации при определении класса объекта-функции. (В главе 13 определение класса рассматривается детально; в главе 15 обсуждается перегрузка операторов.)

В самой простой форме определение класса объекта-функции сводится к перегрузке оператора вызова. Вот, например, унарный объект-функция, определяющий, что некоторое значение меньше или равно 10:

// простейшая форма класса объекта-функции

class less_equal_ten {

public:

bool operator() ( int val )

   { return val < = 10; }

};

Теперь такой объект-функцию можно использовать точно так же, как предопределенный. Вызов алгоритма count_if() с помощью нашего объекта-функции выглядит следующим образом:

count_if( vec.begin(), vec.end(), less_equal_ten() );

Разумеется, возможности этого класса весьма ограничены. Попробуем применить отрицатель, чтобы подсчитать, сколько в контейнере элементов, больших 10:

count_if( vec.begin(), vec.end(),

     not1(less_equal_then ()));

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

class less_equal_value {

public:

less_equal_value( int val ): _val( val ) {}

bool operator() ( int val ) { return val < = _val; }

 

private:

int _val;

};

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

count_if( vec.begin(), vec.end(), less_equal_value( 25 ));

Разрешается реализовать класс и без конструктора, если параметризовать его значением, с которым производится сравнение:

template < int _val >

class less_equal_value {

public:

bool operator() ( int val ) { return val < = _val; }

};

Вот как надо было бы вызвать такой класс для подсчета числа элементов, меньших или равных 25:

count_if( vec.begin(), vec.end(), less_equal_value< 25> ());

(Другие примеры определения собственных объектов-функций можно найти в Приложении.)

Упражнение 12.4

Используя предопределенные объекты-функции и адаптеры, создайте объекты-функции для решения следующих задач:

(a)Найти все значения, большие или равные 1024.

(b)Найти все строки, не равные " pooh".

(c)Умножить все значения на 2.

Упражнение 12.5

Определите объект-функцию для возврата среднего из трех объектов. Определите функцию для выполнения той же операции. Приведите примеры использования каждого объекта непосредственно и путем передачи его функции. Покажите, в чем сходство и различие этих решений.

Еще раз об итераторах

Следующая реализация шаблона функции не компилируется. Можете ли вы сказать, почему?

// в таком виде это не компилируется

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;

}

Проблема в том, что у ссылки vec есть спецификатор const, а мы пытаемся связать с ней итератор без такого спецификатора. Если бы это было разрешено, то ничто не помешало бы нам модифицировать с помощью этого итератора элементы вектора. Для предотвращения подобной ситуации язык требует, чтобы итератор, связанный с const-вектором, был константным. Мы можем сделать это следующим образом:

// правильно: это компилируется без ошибок

vector< type>:: const_iterator iter = vec.begin();

Требование, чтобы с const-контейнером был связан только константный итератор, аналогично требованию о том, чтобы const-массив адресовался только константным указателем. В обоих случаях это вызвано необходимостью гарантировать, что содержимое const-контейнера не будет изменено.

Операции begin() и end() перегружены и возвращают константный или неконстантный итератор в зависимости от наличия спецификатора const в объявлении контейнера. Если дана такая пара объявлений:

vector< int > vec0;

const vector< int > vec1;

то при обращениях к begin() и end() для vec0 будет возвращен неконстантный, а для vec1 – константный итератор:

vector< int >:: iterator iter0 = vec0.begin();

vector< int >:: const_iterator iter1 = vec1.begin();

Разумеется, присваивание константному итератору неконстантного разрешено всегда. Например:

// правильно: инициализация константного итератора неконстантным

vector< int >:: const_iterator iter2 = vec0.begin();

Итераторы вставки

Вот еще один фрагмент программы, в котором есть тонкая, но серьезная ошибка. Видите ли вы, в чем она заключается?

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

Проблема вызвана тем, что алгоритм unique_copy() использует присваивание для копирования значения каждого элемента из вектора ivec, но эта операция завершится неудачно, поскольку в vres не выделено место для хранения девяти целых чисел.

Можно было бы написать две версии алгоритма unique_copy(): одна присваивает элементы, а вторая вставляет их. Эта последняя версия должна, в таком случае, поддерживать вставку в начало, в конец или в произвольное место контейнера.

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

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

// правильно: теперь unique_copy() вставляет элементы с помощью

// vres.push_back()...

unique_copy( ivec.begin(), ivec.end(),

        back_inserter( vres ) );

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

// увы, ошибка:

// класс vector не поддерживает операцию push_front()

// следует использовать контейнеры deque или list

unique_copy( ivec.begin(), ivec.end(),

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

Обратные итераторы

Операции begin() и end() возвращают соответственно итераторы, указывающие на первый элемент и на элемент, расположенный за последним. Можно также вернуть обратный итератор, обходящий контейнер от последнего элемента к первому. Во всех контейнерах для поддержки такой возможности используются операции rbegin() и rend(). Есть константные и неконстантные версии обратных итераторов:

vector< int > vec0;

const vector< int > vec1;

 

vector< int >:: reverse_iterator r_iter0 = vec0.rbegin();

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() пару обратных итераторов:

// сортирует вектор в порядке возрастания

sort( vec0.begin(), vec0.end() );

 

// сортирует вектор в порядке убывания

sort( vec0.rbegin(), vec0.rend() );

Потоковые итераторы

Стандартная библиотека предоставляет средства для работы потоковых итераторов чтения и записи совместно со стандартными контейнерами и обобщенными алгоритмами. Класс istream_iterator поддерживает итераторные операции с классом istream или одним из производных от него, например ifstream для работы с потоком ввода из файла. Аналогично ostream_iterator поддерживает итераторные операции с классом ostream или одним из производных от него, например ofstream для работы с потоком вывода в файл. Для использования любого из этих итераторов следует включить заголовочный файл

#include < iterator>

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

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

}

12.4.4. Итератор istream_iterator

В общем виде объявление потокового итератора чтения istream_iterator имеет форму:

istream_iterator< Type> identifier( istream& ); 1[O.A.3].

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

#include < iterator>

#include < fstream>

#include < string>

#include < complex>

 

// прочитать последовательность объектов типа complex

// из стандартного ввода

istream_iterator< complex > is_complex( cin );

 

// прочитать последовательность строк из именованного файла

ifstream infile( " C++Primer" );

istream_iterator< string > is_string( infile );

При каждом применении оператора инкремента к объекту типа istream_iterator читается следующий элемент из входного потока, для чего используется оператор operator> > (). Чтобы сделать то же самое в обобщенных алгоритмах, необходимо предоставить пару итераторов, обозначающих начальную и конечную позицию в файле. Начальную позицию дает istream_iterator, инициализированный объектом istream, – такой, скажем, как is_string. Для получения конечной позиции мы используем специальный конструктор по умолчанию класса istream_iterator:

// конструирует итератор end_of_stream, который будет служить маркером

// конца потока в итераторной паре

istream_iterator< string > end_of_stream

 

vector< string> text;

 

// правильно: передаем пару итераторов

copy( is_string, end_of_stream,

inserter( text, text.begin() ));

 

12.4.5. Итератор ostream_iterator

Объявление потокового итератора записи ostream_iterator может быть представлено в двух формах:

ostream_iterator< Type> identifier( ostream& )

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

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

#include < iterator>

#include < fstream>

#include < string>

#include < complex>

 

// записать последовательность объектов типа complex

// в стандартный вывод, разделяя элементы пробелами

ostream_iterator< complex > os_complex( cin, " " );

 

// записать последовательность строк в именованный файл

ofstream outfile( " dictionary" );

ostream_iterator< string > os_string( outfile, " \n" );

Вот простой пример чтения из стандартного ввода и копирования на стандартный вывод с помощью безымянных потоковых итераторов и обобщенного алгоритма copy():

#include < iterator>

#include < algorithm>

#include < iostream>

 

int main()

{

copy( istream_iterator< int > ( cin ),

    istream_iterator< int > (),

    ostream_iterator< int > ( cout, " " ));

}

Ниже приведена небольшая программа, которая открывает указанный пользователем файл и копирует его на стандартный вывод, применяя для этого алгоритм copy() и потоковый итератор записи ostream_iterator:

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

}


Поделиться:



Последнее изменение этой страницы: 2019-04-09; Просмотров: 280; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.087 с.)
Главная | Случайная страница | Обратная связь