23.3 – Вывод данных с помощью ostream и ios

Добавлено 5 октября 2021 в 02:52

В этом разделе мы рассмотрим различные аспекты класса библиотеки iostream для вывода данных (ostream).

Примечание. Все функции ввода/вывода в этом уроке находятся в пространстве имен std. Это означает, что все объекты и функции ввода/вывода должны иметь префикс std::, или должна быть использована инструкция using namespace std;.

Оператор вставки

Оператор вставки используется для записи данных в выходной поток. В C++ уже есть предопределенные операции вставки для всех встроенных типов данных, а как перегрузить оператор вставки для пользовательских классов, мы рассмотрели в уроке «13.4 – Перегрузка операторов ввода/вывода».

В уроке о потоках вы видели, что и istream, и ostream были производными от класса с именем ios. Одна из задач iosios_base) – управлять параметрами форматирования вывода.

Форматирование

Есть два способа изменить параметры форматирования: флаги и манипуляторы. Вы можете думать о флагах как о логических переменных, которые можно включать и выключать. Манипуляторы – это объекты, помещенные в поток, которые влияют на способ ввода и вывода.

Чтобы включить флаг, используйте функцию setf() с соответствующим флагом в качестве параметра. Например, по умолчанию C++ не выводит знак + перед положительными числами. Однако, используя флаг std::ios::showpos, мы можем изменить это поведение:

std::cout.setf(std::ios::showpos); // включить флаг std::ios::showpos
std::cout << 27 << '\n';

Это приводит к следующему выводу:

+27

Можно включить сразу несколько флагов ios с помощью оператора ИЛИ (|):

// включить флаги std::ios::showpos и std::ios::uppercase
std::cout.setf(std::ios::showpos | std::ios::uppercase); 
std::cout << 27 << '\n';

Чтобы отключить флаг, используйте функцию unsetf():

std::cout.setf(std::ios::showpos);   // включить флаг std::ios::showpos
std::cout << 27 << '\n';
std::cout.unsetf(std::ios::showpos); // выключить флаг std::ios::showpos
std::cout << 28 << '\n';

Это приводит к следующему выводу:

+27
28

Есть еще одна хитрость при использовании setf(), о которой следует упомянуть. Многие флаги принадлежат группам, называемым группами форматирования. Группа форматирования – это группа флагов, которые управляют аналогичными (иногда взаимоисключающими) параметрами форматирования. Например, группа форматирования с именем basefield содержит флаги oct, dec и hex, которые управляют основанием целочисленных значений. По умолчанию установлен флаг dec. Следовательно, если мы сделаем это:

std::cout.setf(std::ios::hex); // попытаться включить шестнадцатеричный вывод
std::cout << 27 << '\n';

То получим такой вывод:

27

Не сработало! Причина в том, что setf() только включает флаги – этого недостаточно, чтобы отключить взаимоисключающие флаги. Следовательно, когда мы включили std::hex, std::ios::dec всё еще был включен, а std::ios::dec явно имеет приоритет. Есть два способа обойти эту проблему.

Во-первых, мы можем отключить std::ios::dec, чтобы был установлен только std::hex:

std::cout.unsetf(std::ios::dec); // выключаем десятичный вывод
std::cout.setf(std::ios::hex);   // включаем шестнадцатеричный вывод
std::cout << 27 << '\n';

Теперь мы получаем ожидаемый результат:

1b

Второй способ – использовать другую форму setf(), которая принимает два параметра: первый параметр – это флаг, который нужно установить, а второй – это группа форматирования, к которой он принадлежит. При использовании этой формы setf() все флаги, принадлежащие группе, отключаются, и включается только переданный флаг. Например:

// Включаем std::ios::hex как единственный флаг std::ios::basefield
std::cout.setf(std::ios::hex, std::ios::basefield);
std::cout << 27 << '\n';

Это также дает ожидаемый результат:

1b

Использование setf() и unsetf() может быть неудобным, поэтому C++ предоставляет второй способ изменения параметров форматирования: манипуляторы. В манипуляторах хорошо то, что они достаточно умны, чтобы включать и выключать соответствующие флаги. Вот пример использования некоторых манипуляторов для изменения основания чисел:

std::cout << std::hex << 27 << '\n'; // выводим 27 в шестнадцатеричном формате
std::cout << 28 << '\n';             // мы все еще в шестнадцатеричном формате
std::cout << std::dec << 29 << '\n'; // вернуться к десятичной системе счисления

Эта программа создает следующий вывод:

1b
1c
29

В общем, пользоваться манипуляторами намного проще, чем устанавливать и сбрасывать флаги. Многие параметры доступны как через флаги, так и через манипуляторы (например, изменение основания числа), однако некоторые параметры доступны только через флаги или только через манипуляторы, поэтому важно знать, как использовать и то, и другое.

Полезные инструменты форматирования

Вот список некоторых наиболее полезных флагов, манипуляторов и функций-членов. Флаги находятся в классе std::ios, манипуляторы находятся в пространстве имен std, а функции-члены живут в классе std::ostream.

ГруппаФлагНазначение
 std::ios::boolalphaЕсли установлен, логические значения выводят как "true" или "false". Если не установлен, логические значения выводят как 0 или 1.
МанипуляторНазначение
std::boolalphaЛогические значения выводят как "true" или "false".
std::noboolalphaЛогические значения выводят как 0 или 1 (по умолчанию).

Пример:

std::cout << true << " " << false << '\n';

std::cout.setf(std::ios::boolalpha);
std::cout << true << " " << false << '\n';

std::cout << std::noboolalpha << true << " " << false << '\n';

std::cout << std::boolalpha << true << " " << false << '\n';

Результат:

1 0
true false
1 0
true false
ГруппаФлагНазначение
 std::ios::showposЕсли установлен, перед положительными числами ставится +.
МанипуляторНазначение
std::showposДобавляет к положительным числам префикс +
std::noshowposНе ставит перед положительными числами знак +

Пример:

std::cout << 5 << '\n';

std::cout.setf(std::ios::showpos);
std::cout << 5 << '\n';

std::cout << std::noshowpos << 5 << '\n';

std::cout << std::showpos << 5 << '\n';

Результат:

5
+5
5
+5
ГруппаФлагНазначение
 std::ios::uppercaseЕсли установлен, используются буквы верхнего регистра.
МанипуляторНазначение
std::uppercaseИспользует буквы верхнего регистра
std::nouppercaseИспользует буквы нижнего регистра

Пример:

std::cout << 12345678.9 << '\n';

std::cout.setf(std::ios::uppercase);
std::cout << 12345678.9 << '\n';

std::cout << std::nouppercase << 12345678.9 << '\n';

std::cout << std::uppercase << 12345678.9 << '\n';

Результат:

1.23457e+007
1.23457E+007
1.23457e+007
1.23457E+007
ГруппаФлагНазначение
std::ios::basefieldstd::ios::decПечатает значения в десятичном формате (по умолчанию)
std::ios::basefieldstd::ios::hexПечатает значения в шестнадцатеричном формате
std::ios::basefieldstd::ios::octПечатает значения в восьмеричном формате
std::ios::basefield(нет)Печатает значения в соответствии с начальными символами значения
МанипуляторНазначение
std::decПечатает значения в десятичном формате
std::hexПечатает значения в шестнадцатеричном формате
std::octПечатает значения в восьмеричном формате

Пример:

std::cout << 27 << '\n';

std::cout.setf(std::ios::dec, std::ios::basefield);
std::cout << 27 << '\n';

std::cout.setf(std::ios::oct, std::ios::basefield);
std::cout << 27 << '\n';

std::cout.setf(std::ios::hex, std::ios::basefield);
std::cout << 27 << '\n';

std::cout << std::dec << 27 << '\n';
std::cout << std::oct << 27 << '\n';
std::cout << std::hex << 27 << '\n';

Результат:

27
27
33
1b
27
33
1b

К этому моменту вы должны увидеть взаимосвязь между настройкой форматирования с помощью флагов и с помощью манипуляторов. В будущих примерах мы будем использовать манипуляторы, если они доступны.

Точность, формат записи и десятичные точки

Используя манипуляторы (или флаги), можно изменить точность и формат отображения чисел с плавающей запятой. Есть несколько вариантов форматирования, которые комбинируются сложным образом, поэтому мы рассмотрим их подробнее.

ГруппаФлагНазначение
std::ios::floatfieldstd::ios::fixedИспользует десятичную запись для чисел с плавающей запятой
std::ios::floatfieldstd::ios::scientificИспользует экспоненциальную запись для чисел с плавающей запятой
std::ios::floatfield(нет)Использует десятичную запись для чисел с несколькими цифрами, в противном случае – экспоненциальную запись
std::ios::floatfieldstd::ios::showpointВсегда показывать десятичную точку и завершающие нули для значений с плавающей запятой
МанипуляторНазначение
std::fixedИспользует десятичную запись значений
std::scientificИспользует экспоненциальную запись значений
std::showpointПоказывает десятичную точку и завершающие нули для значений с плавающей запятой
std::noshowpointНе показывать десятичную точку и завершающие нули для значений с плавающей запятой
std::setprecision(int)Устанавливает точность чисел с плавающей запятой (определен в iomanip.h)
Функция-членНазначение
std::precision()Возвращает текущую точность чисел с плавающей запятой
std::precision(int)Устанавливает точность чисел с плавающей запятой и возвращает старую точность

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

std::cout << std::fixed << '\n';
std::cout << std::setprecision(3) << 123.456 << '\n';
std::cout << std::setprecision(4) << 123.456 << '\n';
std::cout << std::setprecision(5) << 123.456 << '\n';
std::cout << std::setprecision(6) << 123.456 << '\n';
std::cout << std::setprecision(7) << 123.456 << '\n';

std::cout << std::scientific << '\n';
std::cout << std::setprecision(3) << 123.456 << '\n';
std::cout << std::setprecision(4) << 123.456 << '\n';
std::cout << std::setprecision(5) << 123.456 << '\n';
std::cout << std::setprecision(6) << 123.456 << '\n';
std::cout << std::setprecision(7) << 123.456 << '\n';

Дает следующий результат:

123.456
123.4560
123.45600
123.456000
123.4560000

1.235e+002
1.2346e+002
1.23456e+002
1.234560e+002
1.2345600e+002

Если не используется ни фиксированная, ни экспоненциальная запись, точность определяет, сколько следует отображать значащих цифр. Опять же, если точность меньше количества значащих цифр, число будет округлено.

std::cout << std::setprecision(3) << 123.456 << '\n';
std::cout << std::setprecision(4) << 123.456 << '\n';
std::cout << std::setprecision(5) << 123.456 << '\n';
std::cout << std::setprecision(6) << 123.456 << '\n';
std::cout << std::setprecision(7) << 123.456 << '\n';

Дает следующий результат:

123
123.5
123.46
123.456
123.456

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

std::cout << std::showpoint << '\n';
std::cout << std::setprecision(3) << 123.456 << '\n';
std::cout << std::setprecision(4) << 123.456 << '\n';
std::cout << std::setprecision(5) << 123.456 << '\n';
std::cout << std::setprecision(6) << 123.456 << '\n';
std::cout << std::setprecision(7) << 123.456 << '\n';

Дает следующий результат:

123.
123.5
123.46
123.456
123.4560

Вот сводная таблица с еще несколькими примерами:

Тип записиТочность12345.00.12345
Обычный31.23e+0040.123
41.235e+0040.1235
5123450.12345
6123450.12345
Показывать
десятичную
точку
31.23e+0040.123
41.235e+0040.1235
512345.0.12345
612345.00.123450
Фиксированная
запись
312345.0000.123
412345.00000.1235
512345.000000.12345
612345.0000000.123450
Экспоненциальная
запись
31.235e+0041.235e-001
41.2345e+0041.2345e-001
51.23450e+0041.23450e-001
61.234500e+0041.234500e-001

Ширина, символы заполнения и выравнивание

Обычно при печати чисел числа печатаются без учета пространства вокруг них. Однако печать чисел можно выровнять по левому или правому краю. Для этого мы должны сначала определить ширину поля, которая определяет количество выходных мест печати символов, которые будут заниматься печатаемым значением. Если фактическое напечатанное число меньше ширины поля, оно будет выровнено по левому или правому краю (как указано). Если фактическое число больше ширины поля, оно не будет усечено – оно приведет к переполнению поля.

ГруппаФлагНазначение
std::ios::adjustfieldstd::ios::internalВыравнивает знак числа по левому краю, а значение – по правому краю
std::ios::adjustfieldstd::ios::leftВыравнивание знака и значения по левому краю
std::ios::adjustfieldstd::ios::rightВыравнивает знак и значение по правому краю (по умолчанию)
МанипуляторНазначение
std::internalВыравнивает знак числа по левому краю, а значение – по правому краю
std::leftВыравнивание знака и значения по левому краю
std::rightВыравнивает знак и значение по правому краю
std::setfill(char)Устанавливает параметр как символ заполнения (определен в iomanip.h)
std::setw(int)Устанавливает ширину поля для ввода и вывода в значение параметра (определен в iomanip.h)
Функция-членНазначение
std::fill()Возвращает текущий символ заполнения
std::fill(char)Устанавливает символ заполнения и возвращает старый символ заполнения
std::width()Возвращает текущую ширину поля
std::width(int)Устанавливает новую ширину поля и возвращает старую ширину поля

Чтобы использовать любое из этих средств форматирования, мы сначала должны установить ширину поля. Это можно сделать с помощью функции-члена width(int) или манипулятора setw(). Обратите внимание, что по умолчанию используется выравнивание по правому краю.

// выводим значение по умолчанию без ширины поля
std::cout << -12345 << '\n';

// выводим значение по умолчанию с шириной поля
std::cout << std::setw(10) << -12345 << '\n'; 

// печать с выравниванием по левому краю
std::cout << std::setw(10) << std::left << -12345 << '\n';

// печать с выравниванием по правому краю
std::cout << std::setw(10) << std::right << -12345 << '\n'; 

// печать с внутренним выравниванием
std::cout << std::setw(10) << std::internal << -12345 << '\n';

Это дает следующий результат:

-12345
    -12345
-12345
    -12345
-    12345

Следует отметить, что setw() и width() влияют только на следующую инструкцию вывода. Они не постоянны, как некоторые другие флаги/манипуляторы.

Теперь давайте установим символ заполнения и выполним тот же пример:

std::cout.fill('*');

// выводим значение по умолчанию без ширины поля
std::cout << -12345 << '\n';

// выводим значение по умолчанию с шириной поля
std::cout << std::setw(10) << -12345 << '\n'; 

// печать с выравниванием по левому краю
std::cout << std::setw(10) << std::left << -12345 << '\n';

// печать с выравниванием по правому краю
std::cout << std::setw(10) << std::right << -12345 << '\n'; 

// печать с внутренним выравниванием
std::cout << std::setw(10) << std::internal << -12345 << '\n';

Это дает следующий результат:

-12345
****-12345
-12345****
****-12345
-****12345

Обратите внимание, что все пустые места в поле заполнены символом заполнения.

Класс ostream и библиотека iostream содержат функции вывода, флаги и манипуляторы, которые могут быть полезны в зависимости от того, что вам нужно сделать. Как и в случае с классом istream, эти темы больше подходят для учебника или книги, посвященной стандартной библиотеке.

Теги

C++ / CppiostreamLearnCppstd::iosstd::ostreamВвод/выводДля начинающихМанипулятор выводаОбучениеПрограммирование

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.