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

Программирование на C / C++ / Ален И. Голуб. Правила программирования на Си и Си++ [pdf]

.pdf
Скачиваний:
237
Добавлен:
02.05.2014
Размер:
5.67 Mб
Скачать

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

552

void process_vocab( vector<textwords, allocator> *pvec )

{

//...

//подсчитать число строк, длина которых больше 6

int cnt = count_if( texts.begin(), texts.end(), GreaterThan() );

cout << "Number of words greater than length six are "

<<cnt << endl;

//...

}

Этот фрагмент программы выводит такую строку:

Number of words greater than length six are 22

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

void process_vocab( vector<textwords, allocator> *pvec )

{

// ...

static string rw[] = { "and", "if", "or", "but", "the" }; vector< string > remove_words( rw, rw+5 );

vector< string >::iterator it2 = remove_words.begin(); for ( ; it2 != remove_words.end(); ++it2 ) {

// просто для демонстрации другой формы count()

int cnt = count( texts.begin(), texts.end(), *it2 ); cout << cnt << " instances removed: "

<< (*it2) << endl;

texts.erase( remove(texts.begin(),texts.end(),*it2 ), texts.end()

);

}

// ...

которые мы не хотим сохранять:

}

Результат применения remove():

1 instances removed: and

0 instances removed: if

0 instances removed: or

1 instances removed: but

1 instances removed: the

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

553

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

class PrintElem { public:

PrintElem( int lineLen = 8 )

: _line_length( lineLen ), _cnt( 0 )

{}

void operator()( const string &elem )

{

++_cnt;

if ( _cnt % _line_length == 0 ) { cout << '\n'; }

cout << elem << " ";

}

private:

int _line_length; int _cnt;

PrintElem копирует один элемент в стандартный вывод:

void process_vocab( vector<textwords, allocator> *pvec )

{

// ...

for_each( texts.begin(), texts.end(), PrintElem() );

};

}

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

удобства мы приводим ниже полный листинг вместе с функцией main() для ее тестирования (здесь используются специальные типы итераторов, которые будут обсуждаться только в разделе 12.4). Мы привели текст реально исполнявшегося кода, который не полностью удовлетворяет стандарту C++. В частности, в нашем распоряжении были лишь устаревшие реализации алгоритмов count() и count_if(), которые не возвращают результат, а требуют передачи дополнительного аргумента для вычисленного значения. Кроме того, библиотека iostream отражает предшествующую принятию стандарта реализацию, в которой требуется заголовочный файл iostream.h.

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

554

#include <vector> #include <string> #include <algorithm> #include <iterator>

// предшествующий принятию стандарта синтаксис <iostream> #include <iostream.h>

class GreaterThan { public:

GreaterThan( int size = 6 ) : _size( sz ){} int size() { return _size; }

bool operator()(const string &s1)

{ return s1.size() > _size; }

private:

int _size;

};

class PrintElem { public:

PrintElem( int lineLen = 8 )

: _line_length( lineLen ), _cnt( 0 )

{}

void operator()( const string &elem )

{

++_cnt;

if ( _cnt % _line_length == 0 ) { cout << '\n'; }

cout << elem << " ";

}

private:

int _line_length; int _cnt;

};

bool operator()( const string & s1,

 

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

const string & s2 )

555

{ return s1.size() < s2.size(); }

};

 

 

typedef vector<string, allocator> textwords;

 

void process_vocab( vector<textwords, allocator> *pvec )

 

{

 

 

if ( ! pvec ) {

// вывести предупредительное сообщение return;

}

vector< string, allocator > texts;

vector<textwords, allocator>::iterator iter;

for ( iter = pvec->begin() ; iter != pvec->end(); ++iter ) copy( (*iter).begin(), (*iter).end(),

back_inserter( texts ));

//отсортировать вектор texts sort( texts.begin(), texts.end() );

//теперь посмотрим, что получилось

for_each( texts.begin(), texts.end(), PrintElem() );

cout << "\n\n"; // разделить части выведенного текста

// удалить дубликаты

vector<string, allocator>::iterator it; it = unique( texts.begin(), texts.end() ); texts.erase( it, texts.end() );

// посмотрим, что осталось

for_each( texts.begin(), texts.end(), PrintElem() ); cout << "\n\n";

//отсортировать элементы

//stable_sort сохраняет относительный порядок равных элементов stable_sort( texts.begin(), texts.end(), LessThan() ); for_each( texts.begin(), texts.end(), PrintElem() );

cout << "\n\n";

//подсчитать число строк, длина которых больше 6 int cnt = 0;

//устаревшая форма count - в стандарте используется другая count_if( texts.begin(), texts.end(), GreaterThan(), cnt );

cout << "Number of words greater than length six are " << cnt << endl;

static string rw[] = { "and", "if", "or", "but", "the" }; vector<string,allocator> remove_words( rw, rw+5 );

vector<string, allocator>::iterator it2 = remove_words.begin();

for ( ; it2 != remove_words.end(); ++it2 )

{

int cnt = 0;

// устаревшая форма count - в стандарте используется другая count( texts.begin(), texts.end(), *it2, cnt );

cout <<

cnt

<<

" instances removed: "

<<

(*it2)

<<

endl;

texts.erase(

remove(texts.begin(),texts.end(),*it2),

texts.end()

);

}

cout << "\n\n";

for_each( texts.begin(), texts.end(), PrintElem() );

}

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

556

}

Упражнение 12.2

Длина слова не единственная и, вероятно, не лучшая мера трудности текста. Другой возможный критерий это длина предложения. Напишите программу, которая читает текст из файла либо со стандартного ввода, строит вектор строк для каждого предложения и передает его алгоритму count(). Выведите предложения в порядке сложности. Любопытный способ сделать это сохранить каждое предложение как одну большую строку во втором векторе строк, а затем передать этот вектор алгоритму sort() вместе с объектом-функцией, который считает, что чем строка короче, тем она меньше. (Более подробно с описанием конкретного обобщенного алгоритма, а также с иллюстрацией его применения вы может ознакомиться в Приложении, где все алгоритмы перечислены в алфавитном порядке.)

Упражнение 12.3

Более надежную оценку уровня трудности текста дает анализ структурной сложности предложений. Пусть каждой запятой присваивается 1 балл, каждому двоеточию или точке с запятой – 2 балла, а каждому тире – 3 балла. Модифицируйте программу из упражнения 12.2 так, чтобы она подсчитывала сложность каждого предложения.

Воспользуйтесь алгоритмом count_if() для нахождения каждого из знаков препинания в векторе предложений. Выведите предложения в порядке сложности.

12.3. Объекты-функции

Наша функция min() дает хороший пример как возможностей, так и ограничений

template <typename Type> const Type&

min( const Type *p, int size )

{

Type minval = p[ 0 ];

for ( int ix = 1; ix < size; ++ix ) if ( p[ ix ] < minval )

minval = p[ ix ]; return minval;

механизма шаблонов:

}

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

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

error: invalid types applied to the < operator: Image < Image (ошибка: оператор < применен к некорректным типам: Image < Image)

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

557

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

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

template < typename Type,

bool (*Comp)(const Type&, const Type&)> const Type&

min( const Type *p, int size, Comp comp )

{

Type minval = p[ 0 ];

for ( int ix = 1; ix < size; ++ix ) if ( Comp( p[ ix ] < minval ))

minval = p[ ix ]; return minval;

аргумента и возвращающую значение типа bool:

}

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

Альтернативная стратегия параметризации заключается в применении объекта-функции вместо указателя (примеры мы видели в предыдущем разделе). Объект-функция это класс, перегружающий оператор вызова (operator()). Такой оператор инкапсулирует семантику обычного вызова функции. Объект-функция, как правило, передается обобщенному алгоритму в качестве аргумента, хотя можно определять и независимые объекты-функции. Например, если бы был определен объект-функция AddImages, который принимает два изображения, объединяет их некоторым образом и возвращает новое изображение, то мы могли бы объявить его следующим образом:

AddImages AI;

Чтобы объект-функция удовлетворял нашим требованиям, мы применяем оператор

Image im1("foreground.tiff"), im2("background.tiff");

//...

//вызывает Image AddImages::operator()(const Image1&, const Image2&);

вызова, предоставляя необходимые операнды в виде объектов класса Image:

Image new_image = AI (im1, im2 );

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

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

558

количество дополнительных данных, например кэш или информацию, полезную для

выполнения текущей операции.

 

Ниже приведена измененная реализация шаблона min() (отметим,

что это объявление

template < typename Type, typename Comp >

const Type&

min( const Type *p, int size, Comp comp )

{

Type minval = p[ 0 ];

for ( int ix = 1; ix < size; ++ix ) if ( Comp( p[ ix ] < minval ))

minval = p[ ix ]; return minval;

допускает также и передачу указателя на функцию, но без проверки прототипа):

}

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

Есть три источника появления объектов-функций:

1.из набора предопределенных арифметических, сравнительных и логических объектов-функций стандартной библиотеки;

2.из набора предопределенных адаптеров функций, позволяющих специализировать или расширять предопределенные (или любые другие) объекты-функции;

3.определенные нами собственные объекты-функции для передачи обобщенным алгоритмам. К ним можно применять и адаптеры функций.

В этом разделе мы рассмотрим все три источника объектов-функций.

12.3.1. Предопределенные объекты-функции

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

#include <functional>

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

#include <functional>

написать:

plus< int > intAdd;

Для выполнения операции сложения мы применяем перегруженный оператор вызова к intAdd точно так же, как и к классу AddImage в предыдущем разделе:

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

559

int ival1 = 10, ival2 = 20;

// эквивалентно int sum = ival1 + ival2; int sum = intAdd( ival1, ival2 );

Реализация шаблона класса plus вызывает оператор сложения, ассоциированный с типом своего параметра int. Этот и другие предопределенные объекты-функции

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

алгоритм sort() располагает элементы контейнера в порядке возрастания с помощью оператора меньшебазового типа. Для сортировки по убыванию мы передаем

vector< string > svec;

// ...

предопределенный шаблон класса greater, который вызывает оператор больше”: sort( svec.begin(), svec.end(), greater<string>() );

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

class Int { public:

Int( int ival = 0 ) : _val( ival ) {}

 

int operator-()

{ return -_val;

}

 

int operator%(int ival)

{ return -_val % ival;

}

 

bool operator<(int ival)

{ return -_val < ival;

}

 

bool operator!()

{ return -_val == 0;

}

 

private:

 

 

 

int _val;

 

 

};

 

 

 

 

vector< string > svec;

 

 

 

string

sval1, sval2, sres;

 

 

 

complex cval1, cval2, cres;

 

 

 

int

ival1, ival2, ires;

 

 

 

Int

Ival1, Ival2, Ires;

 

 

15):

 

 

 

 

double

dval1, dval2, dres;

 

 

 

 

 

 

 

 

 

 

Кроме того, мы определяем два шаблона функций, которым передаем различные безымянные объекты-функции:

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

560

 

template <class FuncObject, class Type>

 

 

 

 

Type UnaryFunc( FuncObject fob, const Type &val )

 

 

{ return fob( val ); }

 

 

template <class FuncObject, class Type>

 

 

Type BinaryFunc( FuncObject fob,

 

 

const Type &val1, const Type &val2 )

 

 

{ return fob( val1, val2 ); }

 

 

 

 

 

 

12.3.2. Арифметические объекты-функции

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

plus<string> stringAdd;

// вызывается string::operator+() sres = stringAdd( sval1, sval2 );

∙ Сложение: plus<Type>

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

minus<int> intSub;

ires = intSub( ival1, ival2 );

∙ Вычитание: minus<Type>

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

multiplies<complex> complexMultiplies; cres = complexMultiplies( cval1, cval2 );

∙ Умножение: multiplies<Type>

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

divides<int> intDivides;

ires = intDivides( ival1, ival2 );

∙ Деление: divides<Type>

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

Взятие остатка: modulus<Type>

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

561

modulus<Int> IntModulus;

Ires = IntModulus( Ival1, Ival2 );

ires = BinaryFunc( modulus<int>(), ival1, ival2 );

negate<int> intNegate; ires = intNegate( ires );

∙ Вычисление противоположного значения: negate<Type>

Ires = UnaryFunc( negate<Int>(), Ival1 );

12.3.3. Сравнительные объекты-функции

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

equal_to<string> stringEqual;

sres = stringEqual( sval1, sval2 );

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

Равенство: equal_to<Type>

equal_to<string>(), sval1 );

not_equal_to<complex> complexNotEqual; cres = complexNotEqual( cval1, cval2 ); ires = count_if( svec.begin(), svec.end(),

Неравенство: not_equal_to<Type>

not_equal_to<string>(), sval1 );

greater<int> intGreater;

ires = intGreater( ival1, ival2 );

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

Больше: greater<Type>

greater<string>(), sval1 );

greater_equal<double> doubleGreaterEqual; dres = doubleGreaterEqual( dval1, dval2 ); ires = count_if( svec.begin(), svec.end(),

Больше или равно: greater_equal<Type>