Язык программирования C++ для профессионалов

       

Манипуляторы


К ним относятся разнообразные операции, которые приходится применять сразу перед или сразу после операции ввода-вывода. Например:

cout << x; cout.flush(); cout << y;

cin.eatwhite(); cin >> x;

Если писать отдельные операторы как выше, то логическая связь между операторами неочевидна, а если утеряна логическая связь, программу труднее понять.

Идея манипуляторов позволяет такие операции как flush() или eatwhite() прямо вставлять в список операций ввода-вывода. Рассмотрим операцию flush(). Можно определить класс с операцией operator<<(), в котором вызывается flush():

class Flushtype { };

ostream& operator<<(ostream& os, Flushtype) { return flush(os); }

определить объект такого типа

Flushtype FLUSH;

и добиться выдачи буфера, включив FLUSH в список объектов, подлежащих выводу:

cout << x << FLUSH << y << FLUSH ;

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

typedef ostream& (*Omanip) (ostream&);

ostream& operator<<(ostream& os, Omanip f) { return f(os); }

Здесь операция вывода использует параметры типа "указатель на функцию, имеющую аргумент ostream& и возвращающую ostream&". Отметив, что flush() есть функция типа "функция с аргументом ostream& и возвращающая ostream&", мы можем писать

cout << x << flush << y << flush;

получив вызов функции flush(). На самом деле в файле <iostream.h> функция flush() описана как



ostream& flush(ostream&);

а в классе есть операция operator<<, которая использует указатель на функцию, как указано выше:

class ostream : public virtual ios { // ... public: ostream& operator<<(ostream& ostream& (*)(ostream&)); // ... };

В приведенной ниже строке буфер выталкивается в поток cout дважды в подходящее время:


cout << x << flush << y << flush;

Похожие определения существуют и для класса istream:

istream& ws(istream& is ) { return is.eatwhite(); }

class istream : public virtual ios { // ... public: istream& operator>>(istream&, istream& (*) (istream&)); // ... };

поэтому в строке

cin >> ws >> x;

действительно обобщенные пробелы будут убраны до попытки чтения в x. Однако, поскольку по умолчанию для операции >> пробелы "съедаются" и так, данное применение ws() избыточно.

Находят применение и манипуляторы с параметрами. Например, может появиться желание с помощью

cout << setprecision(4) << angle;

напечатать значение вещественной переменной angle с точностью до четырех знаков после точки.

Для этого нужно уметь вызывать функцию, которая установит значение переменной, управляющей в потоке точностью вещественных. Это достигается, если определить setprecision(4) как объект, который можно "выводить" с помощью operator<<():

class Omanip_int { int i; ostream& (*f) (ostream&,int); public: Omanip_int(ostream& (*ff) (ostream&,int), int ii) : f(ff), i(ii) { } friend ostream& operator<<(ostream& os, Omanip& m) { return m.f(os,m.i); } };

Конструктор Omanip_int хранит свои аргументы в i и f, а с помощью operator<< вызывается f() с параметром i. Часто объекты таких классов называют объект-функция. Чтобы результат строки

cout << setprecision(4) << angle

был таким, как мы хотели, необходимо чтобы обращение setprecision(4) создавало безымянный объект класса Omanip_int, содержащий значение 4 и указатель на функцию, которая устанавливает в потоке ostream значение переменной, задающей точность вещественных:

ostream& _set_precision(ostream&,int);

Omanip_int setprecision(int i) { return Omanip_int(&_set_precision,i); }

Учитывая сделанные определения, operator<<() приведет к вызову precision(i).

Утомительно определять классы наподобие Omanip_int для всех типов аргументов, поэтому определим шаблон типа:


Содержание раздела