С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 96
Текст из файла (страница 96)
Другойвозможный критерий – это длина предложения. Напишите программу, которая читаеттекст из файла либо со стандартного ввода, строит вектор строк для каждогопредложения и передает его алгоритму 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)556С++ для начинающихВозможна и другая ситуация: оператор “меньше” существует, но имеет неподходящуюсемантику.
Например, если мы хотим найти наименьшую строку, но при этом приниматьво внимание только буквы, не учитывая регистр, то такой реализованный в классеоператор не даст нужного результата.Традиционное решение состоит в том, чтобы параметризовать оператор сравнения. Вданном случае это можно сделать, объявив указатель на функцию, принимающую два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 );У объекта-функции есть два преимущества по сравнению с указателем на функцию.
Вопервых, если перегруженный оператор вызова – это встроенная функция, то компиляторможет выполнить ее подстановку, обеспечивая значительный выигрыш впроизводительности. Во-вторых, объект-функция способен содержать произвольное557С++ для начинающих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 в предыдущем разделе:С++ для начинающих559int 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-()int operator%(int ival){ return -_val;{ return -_val % ival;}}bool operator<(int ival) { return -_val < ival;bool operator!(){ return -_val == 0;private:int _val;};}}vector<stringcomplexintIntstringsval1,cval1,ival1,Ival1,> svec;sval2, sres;cval2, cres;ival2, ires;Ival2, Ires;15):doubledval1, dval2, dres;Кроме того, мы определяем два шаблона функций, которым передаем различныебезымянные объекты-функции:С++ для начинающих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>560С++ для начинающих561modulus<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.