Бьерн Страуструп.
Язык программирования С++
271
1.234568e+03
1234.567890
После точки печатается n цифр, как задается в обращении
cout.precision(n)
По умолчанию n равно 6. Вызов функции precision влияет на
все операции ввода-вывода с
вещественными до следующего обращения к precision, поэтому
cout.precision(8);
cout << 1234.56789 << '\n';
cout << 1234.56789 << '\n';
cout.precision(4);
cout << 1234.56789 << '\n';
cout << 1234.56789 << '\n';
выдаст
1234.5679
1234.5679
1235
1235
Заметьте, что происходит округление, а не отбрасывание дробной части.
Стандартные манипуляторы, введенные в $$10.4.2.1, предлагают более элегантный способ задания
формата вывода вещественных.
10.4.2 Манипуляторы
К ним относятся разнообразные операции, которые приходится применять сразу перед или сразу после
операции ввода-вывода. Например:
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&);
Бьерн Страуструп.
Язык программирования С++
272
ostream& operator<<(ostream& os, Omanip f)
{
return
f(os);
}
Здесь операция вывода использует параметры типа "указатель на
функцию, имеющую аргумент
ostream& и возвращающую ostream&". Отметив, что flush() есть функция типа "функция с аргументом
ostream& и возвращающая ostream&", мы можем писать
cout << x << flush << y << flush;
получив вызов функции flush(). На самом деле в файле
функция 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);
}
};
Бьерн Страуструп.
Язык программирования С++
273
Конструктор 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 для всех типов аргументов, поэтому определим
шаблон типа:
template
class OMANIP {
T
i;
ostream& (*f) (ostream&,T);
public:
OMANIP(ostream (*ff) (ostream&,T), T ii)
: f(ff), i(ii) { }
friend ostream& operator<<(ostream& os, OMANIP& m)
{
return
m.f(os,m.i)
}
};
С помощью OMANIP пример с установкой точности можно сократить так:
ostream& precision(ostream& os,int)
{
os.precision(i);
return
os;
}
OMANIP setprecision(int i)
{
return
OMANIP(&precision,i);
}
В файле можно найти шаблон типа OMANIP, его двойник для istream - шаблон типа
SMANIP, а SMANIP - двойник для ioss. Некоторые из стандартных манипуляторов, предлагаемых
поточной библиотекой, описаны ниже. Отметим,что программист может определить новые
необходимые ему манипуляторы, не затрагивая определений istream, ostream, OMANIP или SMANIP.
Идею манипуляторов предложил А. Кениг. Его вдохновили процедуры разметки (layout ) системы ввода-
вывода Алгола68. Такая техника имеет много интересных приложений помимо ввода-вывода. Суть ее в
том, что создается объект, который можно передавать куда угодно и который используется как функция.
Передача объекта является более гибким решением, поскольку детали выполнения частично
определяются создателем объекта, а частично тем, кто к нему обращается.
Достарыңызбен бөлісу: