Форматирование
Все примеры из 10.2 содержали неформатированный вывод, который являлсяпреобразованием объекта в последовательность символов, задаваемуюстандартными правилами, длина которой также определяется этимиправилами. Часто программистам требуются более развитые возможности.Так, возникает потребность контролировать размер памяти, необходимойдля операции вывода, и формат, используемый для выдачи чисел.Точно так же допустимо управление некоторыми аспектами ввода.Класс ios
Большинство средств управления вводом-выводом сосредоточены в классеios, который является базовым для ostream и istream. По сути здесьнаходится управление связью между istream или ostream и буфером,используемым для операций ввода-вывода. Именно класс ios контролирует:как символы попадают в буфер и как они выбираются оттуда. Так, в классеios есть член, содержащий информацию об используемой при чтении илизаписи целых чисел системы счисления (десятичная, восьмеричная илишестнадцатеричная), о точности вещественных чисел и т.п., а такжефункции для проверки и установки значений переменных, управляющихпотоком. class ios { //... public: ostream* tie(ostream* s); // связать input и output ostream* tie(); // возвратить "tie" int width(int w); // установить поле width int width() const; char fill(char); // установить символ заполнения char fill() const; // вернуть символ заполнения long flags(long f); long flags() const; long setf(long setbits, long field); long setf(long); long unsetf(long); int precision(int); // установить точность для float int precision() const; int rdstate(); const; // состояния потоков, см. $$10.3.2 int eof() const; int fail() const; int bad() const; int good() const; void clear(int i=0); //... }; В 10.3.2 описаны функции, работающие с состоянием потока, остальныеприведены ниже.Связывание потоков
Поля вывода
Состояние формата
Вывод целых
Прием задания нового значения множества флагов с помощью операции | ифункций flags() и setf() работает только тогда, когда один бит определяетзначение флага. Не такая ситуация при задании системы счисления целыхили вида выдачи вещественных. Здесь значение, определяющее вид выдачи,нельзя задать одним битом или комбинацией отдельных битов. Решение, принятое в <iostream.h>, сводится к использованиюверсии функции setf(), работающей со вторым "псевдопараметром", которыйпоказывает какой именно флаг мы хотим добавить к новому значению. Поэтому обращения cout.setf(ios::oct,ios::basefield); // восьмеричное cout.setf(ios::dec,ios::basefield); // десятичное cout.setf(ios::hex,ios::basefield); // шестнадцатеричное установят систему счисления, не затрагивая других компонентов состоянияпотока. Если система счисления установлена, она используется до явнойпереустановки, поэтому cout << 1234 << ' '; // десятичное по умолчанию cout << 1234 << ' '; cout.setf(ios::oct,ios::basefield); // восьмеричное cout << 1234 << ' '; cout << 1234 << ' '; cout.setf(ios::hex,ios::basefield); // шестнадцатеричное cout << 1234 << ' '; cout << 1234 << ' '; напечатает 1234 1234 2322 2322 4d2 4d2 Если появится необходимость указывать систему счисления для каждоговыдаваемого числа, следует установить флаг showbase. Поэтому, добавивперед приведенными выше обращениями cout.setf(ios::showbase); мы получим 1234 1234 02322 02322 0x4d2 0x4d2 Стандартные манипуляторы, приведенные в $$10.4.2.1, предлагают болееэлегантный способ определения системы счисления при выводе целых.Выравнивание полей
С помощью обращений к setf() можно управлять расположением символовв пределах поля: cout.setf(ios::left,ios::adjustfield); // влево cout.setf(ios::right,ios::adjustfield); // вправо cout.setf(ios::internal,ios::adjustfield); // внутреннее Будет установлено выравнивание в поле вывода, определяемом функциейios::width(), причем не затрагивая других компонентов состояния потока. Выравнивание можно задать следующим образом: cout.width(4); cout << '(' << -12 << ")\n"; cout.width(4); cout.setf(ios::left,ios::adjustfield); cout << '(' << -12 << ")\n"; cout.width(4); cout.setf(ios::internal,ios::adjustfield); cout << '(' << -12 << "\n"; что выдаст (-12) (-12) (- 12) Если установлен флаг выравнивания internal (внутренний), то символыдобавляются между знаком и величиной. Как видно, стандартным являетсявыравнивание вправо.Вывод плавающих чисел.
Вывод вещественных величин также управляется с помощью функций,работающих с состоянием потока. В частности, обращения: cout.setf(ios::scientific,ios::floatfield); cout.setf(ios::fixed,ios::floatfield); cout.setf(0,ios::floatfield); // вернуться к стандартному установят вид печати вещественных чисел без изменения другихкомпонентов состояния потока.Например: cout << 1234.56789 << '\n'; cout.setf(ios::scientific,ios::floatfield); cout << 1234.56789 << '\n'; cout.setf(ios::fixed,ios::floatfield); cout << 1234.56789 << '\n'; напечатает 1234.57 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, предлагаютболее элегантный способ задания формата вывода вещественных.Манипуляторы
К ним относятся разнообразные операции, которые приходитсяприменять сразу перед или сразу после операции ввода-вывода. Например: 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 для всехтипов аргументов, поэтому определим шаблон типа: template<class T> 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<int> setprecision(int i) { return OMANIP<int>(&precision,i); } В файле <iomanip.h> можно найти шаблон типа OMANIP, его двойник дляistream - шаблон типа SMANIP, а SMANIP - двойник для ioss.Некоторые из стандартных манипуляторов, предлагаемых поточнойбиблиотекой, описаны ниже. Отметим,что программист может определить новыенеобходимые ему манипуляторы, не затрагивая определений istream,ostream, OMANIP или SMANIP. Идею манипуляторов предложил А. Кениг. Его вдохновили процедурыразметки (layout) системы ввода-вывода Алгола68. Такая техника имеетмного интересных приложений помимо ввода-вывода. Суть ее в том, чтосоздается объект, который можно передавать куда угодно и которыйиспользуется как функция. Передача объекта является более гибкимрешением, поскольку детали выполнения частично определяются создателемобъекта, а частично тем, кто к нему обращается.