Чтение онлайн

ЖАНРЫ

C++. Сборник рецептов

Когсуэлл Джефф

Шрифт:

 double pi = 22.0/7.0;

 cout << pi = " << scientific // Научный режим

<< pi * 1000 << '\n';

 cout << "pi = " << floatnormal << pi << '\n';

 cout.flags(flags);

}

Обсуждение

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

Прочитав рецепт 10.1, вероятно, вы поняли, что существует три формата вывода чисел с плавающей точкой и только два манипулятора для выбора формата. Для используемого по умолчанию формата не предусмотрен манипулятор; вам придется соответствующим образом установить флаг для потока, чтобы вернуться к стандартному формату:

myiostr.setf(0, ios_base::float field);

Но для удобства вы можете добавить свой собственный манипулятор, делающий то же самое. Именно это сделано в примере 10.4. Манипулятор

floatnormal
устанавливает соответствующий флаг потока для вывода чисел с плавающей точкой в стандартном формате.

Компилятор знает, что делать с вашей новой функцией, потому что в стандартной библиотеке определен следующий оператор для

basic_ostream
(
basic_ostream
— имя шаблона класса, инстанцируемого в классах
ostream
и
wostream
).

basic_ostream<charT, traits>& operator<<

 (basic_ostream<charT, traits>& (* pf)basic_ostream<charT, traits>&))

Здесь

pf
— это указатель функции, которая принимает в аргументе ссылку на
basic_ostream
и возвращает ссылку на
basic_ostream
. Этот оператор просто обеспечивает вызов вашей функции, которая принимает в качестве аргумента текущий поток.

Манипуляторы с аргументами более сложные. Чтобы понять причину этого, рассмотрим работу манипулятора без аргументов. Пусть используется, например, следующий манипулятор

myostream << myManip << "foo";

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

operator<<
вызывает функцию манипулятора и передает ей поток, чтобы манипулятор мог выполнить свою работу.

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

myostream << myFancyManip(17) << "apple";

Как это будет работать? Если вы считаете, что

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

myostream << myFancyManip(17, myostream) << "apple";

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

operator<<
, и они хорошо воспринимаются и используются.

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

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

Во-первых, вам необходимо определить временный класс, который делал бы всю работу. Для простоты предположим, что вам требуется написать манипулятор с именем

setWidth
, который делает то же самое, что и
setw
. Временная структура, которую вам необходимо построить, будет выглядеть примерно так.

class WidthSetter {

public:

 WidthSetter(int n) : width_(n) {}

 void operator(ostream& os) const {os.width(width_);}

private:

 int width_;

};

Этот класс содержит простую функцию. Предусмотрите в ней целочисленный аргумент и, когда

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

WidthSetter setWidth(int n) {

 return(WidthSetter(n)); // Возвращает инициализированный объект

}

Эта функция всего лишь возвращает объект

WidthSetter
, инициализированный целым значением. Этот манипулятор вы будете использовать в строке операторов
operator<<
следующим образом.

myostream << setWidth(20) << "banana";

Но этого недостаточно, потому что

setWidth
просто возвращает объект
WidthSetter
;
operator<<
не будет знать, что с ним делать. Вам придется перегрузить
operator<<
, чтобы он знал, как управлять объектом
WidthSetter
:

ostream& operator<<(ostream& os, const WidthSetter& ws) {

 ws(os); // Передать поток объекту ws

 return(os); // для выполнения реальной работы

}

Это решает проблему, но не в общем виде. Вам не захочется писать класс типа

WidthSetter
для каждого вашего манипулятора, принимающего аргумент (возможно, вы это и делаете, но были бы не против поступить по-другому), поэтому лучше использовать шаблоны и указатели функций для получения привлекательной, обобщенной инфраструктуры, на базе которой вы можете создавать любое количество манипуляторов. Пример 10.5 содержит класс
ManipInfra
и версию
operator<<
, использующую аргументы шаблона для работы с различными типами символов, которые может содержать поток, и с различными типами аргументов, которые могут быть использованы манипулятором потока.

Пример 10.5. Инфраструктура манипуляторов

#include <iostream>

#include <string>

using namespace std;

// ManipInfra - это небольшой промежуточный класс, который помогает

// создавать специальные манипуляторы с аргументами. Вызывайте его

// конструктор с предоставлением указателя функции и значения из основной

// функции манипулятора

Поделиться с друзьями: