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

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

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

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

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>

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