Программирование на C / C++ / Ален И. Голуб. Правила программирования на Си и Си++ [pdf]
.pdfС++ для начинающих |
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 для работы с потоком вывода в файл. Для использования любого из этих итераторов следует включить
заголовочный файл
С++ для начинающих |
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: