Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Язык Программирования C++. Вводный курс [rus]

.pdf
Скачиваний:
1161
Добавлен:
16.08.2013
Размер:
6 Мб
Скачать
☆

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

562

greater_equal <string>(), sval1 );

less<Int> IntLess;

Ires = IntLess( Ival1, Ival2 );

ires = count_if( svec.begin(), svec.end(),

∙Меньше: less<Type>

less<string>(), sval1 );

less_equal<int> intLessEqual;

ires = intLessEqual( ival1, ival2 );

ires = count_if( svec.begin(), svec.end(),

∙Меньше или равно: less_equal<Type>

less_equal<string>(), sval1 );

12.3.4. Логические объекты-функции

Логические объекты-функции поддерживают операции “логическое И” (возвращает true, если оба операнда равны true, – применяет оператор &&, аcсоциированный с типом Type), “логическое ИЛИ” (возвращает true, если хотя бы один из операндов равен true, – применяет оператор ||, аcсоциированный с типом Type) и “логическое НЕ” (возвращает true, если операнд равен false, – применяет оператор !, аcсоциированный с типом Type)

logical_and<int> intAnd;

ires = intLess( ival1, ival2 );

∙Логическое И: logical_and<Type>

dres = BinaryFunc( logical_and<double>(), dval1, dval2 );

logical_or<int> intSub;

ires = intSub( ival1, ival2 );

∙Логическое ИЛИ: logical_or<Type>

dres = BinaryFunc( logical_or<double>(), dval1, dval2 );

logical_not<Int> IntNot;

ires = IntNot( Ival1, Ival2 );

∙Логическое НЕ: logical_not<Type>

dres = UnaryFunc( logical_or<double>(), dval1 );

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

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( vec.begin(), vec.end(),

алгоритму count_if() следующее:

bind2nd( less_equal<int>(), 10 ));

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

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

следующее:

not1( bind2nd( less_equal<int>(), 10 )));

Другие примеры использования связывателей и отрицателей приведены в Приложении, вместе с примерами использования каждого алгоритма.

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

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

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

564

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

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

public:

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

некоторое значение меньше или равно 10:

};

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

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

Разумеется, возможности этого класса весьма ограничены. Попробуем применить

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

отрицатель, чтобы подсчитать, сколько в контейнере элементов, больших 10: 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 ));

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

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

565

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

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

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

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

// в таком виде это не компилируется 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, а мы пытаемся связать с ней итератор без такого спецификатора. Если бы это было разрешено, то ничто не помешало бы нам модифицировать с помощью этого итератора элементы вектора. Для

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

566

предотвращения подобной ситуации язык требует, чтобы итератор, связанный с const-

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

вектором, был константным. Мы можем сделать это следующим образом: vector< type>::const_iterator iter = vec.begin();

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

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

vector< int > vec0;

дана такая пара объявлений: const vector< int > vec1;

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

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

vec1 – константный итератор:

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

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

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

Например:

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

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

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

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

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

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 или list

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

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

568

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 для работы с потоком вывода в файл. Для использования любого из этих итераторов следует включить

заголовочный файл

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

569

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

Примечание [O.A.3]: Нумера ция сносок сбита.

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

570

где 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 );

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

571

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:

Соседние файлы в предмете Программирование на C++