Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Введение в С++. Страуструп..doc
Скачиваний:
63
Добавлен:
02.05.2014
Размер:
3.46 Mб
Скачать

10.4.1.4 Вывод целых

Прием задания нового значения множества флагов с помощью операции | и

функций 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, предлагают более

элегантный способ определения системы счисления при выводе целых.

10.4.1.5 Выравнивание полей

С помощью обращений к 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 (внутренний), то символы

добавляются между знаком и величиной. Как видно, стандартным является

выравнивание вправо.

10.4.1.6 Вывод плавающих чисел.

Вывод вещественных величин также управляется с помощью функций,

работающих с состоянием потока. В частности, обращения:

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, предлагают

более элегантный способ задания формата вывода вещественных.

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&);

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, его двойник для