На цьому уроці ми розглянемо функціонал класів ostream
і ios
в мові С++.
Примітка: Весь функціонал об’єктів, які працюють з потоками вводу/виводу, знаходиться в просторі імен std. Це означає, що вам потрібно або додавати префікс std::
до всіх об’єктів і функцій вводу/виводу, або використовувати в програмі рядок using namespace std;
.
Форматування виводу
Оператор вставки (виводу) <<
використовується для переміщення інформації в вихідний потік. А як ми вже знаємо з уроку про потоки, класи istream
і ostream
є дочірніми класу ios
. Одним із завдань ios
(і ios_base
) є управління параметрами форматування виводу.
Є два способи управління параметрами форматування виводу:
флаги — це логічні змінні, які можна увімкнути/вимкнути;
маніпулятори — це об’єкти, які поміщаються в потік і впливають на спосіб вводу/виводу даних.
Для увімкнення флага використовуйте функцію setf() з відповідним флагом в якості параметра. Наприклад, за замовчуванням мова C++ не виводить знак +
перед додатними числами. Однак, використовуючи флаг std::showpos, ми можемо це змінити:
1 2 3 4 5 6 7 |
#include <iostream> int main() { std::cout.setf(std::ios::showpos); // вмикаємо флаг std::showpos std::cout << 30 << '\n'; } |
Результат:
+30
Також можна увімкнути відразу кілька флагів, використовуючи побітовий оператор АБО (|
):
1 2 3 4 5 6 7 |
#include <iostream> int main() { std::cout.setf(std::ios::showpos | std::ios::uppercase); // вмикаємо флаги std::showpos і std::uppercase std::cout << 30 << '\n'; } |
Щоб вимкнути флаг, використовуйте функцію unsetf():
1 2 3 4 5 6 7 8 9 |
#include <iostream> int main() { std::cout.setf(std::ios::showpos); // вмикаємо флаг std::showpos std::cout << 30 << '\n'; std::cout.unsetf(std::ios::showpos); // вимикаємо флаг std::showpos std::cout << 31 << '\n'; } |
Результат:
+30
31
Багато флагів належать до певних груп форматування. Група форматування — це група флагів, які задають аналогічні (іноді взаємовиключні) параметри форматування виводу. Наприклад, є група форматування basefield.
Флаги групи форматування basefield:
oct (від англ. “octal” = “вісімковий”) — вісімкова система числення;
dec (від англ. “decimal” = “десятковий”) — десяткова система числення;
hex (від англ. “hexadecimal” = “шістнадцятковий”) — шістнадцяткова система числення.
Ці флаги керують виводом цілочисельних значень. За замовчуванням встановлено флаг std::dec, тобто значення виводяться в десятковій системі числення. Спробуємо зробити наступне:
1 2 3 4 5 6 7 |
#include <iostream> int main() { std::cout.setf(std::ios::hex); // вмикаємо флаг std::hex std::cout << 30 << '\n'; } |
Результат:
30
Нічого не працює! Чому? Справа в тому, що setf() тільки вмикає флаги, він не настільки розумний, щоб одночасно вимикати інші (взаємовиключні) флаги. Отже, коли ми вмикаємо std::hex, std::dec також ввімкнений і у нього більший пріоритет. Є два способи вирішення даної проблеми.
По-перше, ми можемо вимкнути std::dec, а потім увімкнути std::hex:
1 2 3 4 5 6 7 8 |
#include <iostream> int main() { std::cout.unsetf(std::ios::dec); // вимикаємо вивід в десятковій системі числення std::cout.setf(std::ios::hex); // вмикаємо вивід в шістнадцятковій системі числення std::cout << 30 << '\n'; } |
Тепер вже результат той, що потрібно:
1e
Другий спосіб — використовувати варіацію функції setf(), яка приймає два параметри:
перший параметр — це флаг, який потрібно увімкнути/вимкнути;
другий параметр — група форматування, до якої належить флаг.
При використанні цієї варіації функції setf() всі флаги, які належать групі форматування, вимикаються, а вмикається тільки переданий флаг. Наприклад:
1 2 3 4 5 6 7 8 |
#include <iostream> int main() { // Вмикаємо і залишаємо увімкненим єдиний флаг (std::hex) групи форматування std::basefield std::cout.setf(std::ios::hex, std::ios::basefield); std::cout << 30 << '\n'; } |
Результат:
1e
Мова C++ також надає ще один спосіб зміни параметрів форматування: маніпулятори. Фішка маніпуляторів в тому, що вони достатньо розумні, щоб одночасно вмикати і вимикати відповідні флаги. Наприклад:
1 2 3 4 5 6 7 8 |
#include <iostream> int main() { std::cout << std::hex << 30 << '\n'; // виводимо 30 в шістнадцятковій системі числення std::cout << 31 << '\n'; // ми все ще знаходимося в шістнадцятковій системі числення std::cout << std::dec << 32 << '\n'; // переміщаємося назад в десяткову систему числення } |
Результат:
1e
1f
32
Загалом, використовувати маніпулятори набагато простіше, ніж вмикати/вимикати флаги. Багато параметрів форматування можна змінювати як через флаги, так і через маніпулятори, але є і такі параметри форматування, які змінити можна або тільки через флаги, або тільки через маніпулятори.
Корисні флаги, маніпулятори і методи
Нижче ми розглянемо список найбільш корисних флагів, маніпуляторів і методів. Флаги знаходяться в класі ios
, маніпулятори — в просторі імен std, а методи — в класі ostream
.
Флаг:
boolalpha — якщо увімкнений, то логічні значення виводяться як true
/false
. Якщо вимкнений, то логічні значення виводяться як 0
/1
.
Маніпулятори:
boolalpha — логічні значення виводяться як true
/false
.
noboolalpha — логічні значення виводяться як 0
/1
.
Наприклад:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <iostream> int main() { 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
Флаг:
showpos — якщо увімкнений, то перед додатними числами вказується знак +
.
Маніпулятори:
showpos — перед додатними числами вказується знак +
.
noshowpos — перед додатними числами не вказується знак +
.
Наприклад:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <iostream> int main() { std::cout << 7 << '\n'; std::cout.setf(std::ios::showpos); std::cout << 7 << '\n'; std::cout << std::noshowpos << 7 << '\n'; std::cout << std::showpos << 7 << '\n'; } |
Результат:
7
+7
7
+7
Флаг:
uppercase — якщо увімкнений, то використовуються заголовні букви.
Маніпулятори:
uppercase — використовуються заголовні букви.
nouppercase — використовуються рядкові (малі) букви.
Наприклад:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <iostream> int main() { 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
Флаги групи форматування basefield:
dec — значення виводяться в десятковій системі числення;
hex — значення виводяться в шістнадцятковій системі числення;
oct — значення виводяться в вісімковій системі числення.
Маніпулятори:
dec — значення виводяться в десятковій системі числення;
hex — значення виводяться в шістнадцятковій системі числення;
oct — значення виводяться в вісімковій системі числення.
Наприклад:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include <iostream> int main() { std::cout << 30 << '\n'; std::cout.setf(std::ios::dec, std::ios::basefield); std::cout << 30 << '\n'; std::cout.setf(std::ios::oct, std::ios::basefield); std::cout << 30 << '\n'; std::cout.setf(std::ios::hex, std::ios::basefield); std::cout << 30 << '\n'; std::cout << std::dec << 30 << '\n'; std::cout << std::oct << 30 << '\n'; std::cout << std::hex << 30 << '\n'; } |
Результат:
30
30
36
1e
30
36
1e
Тепер ви вже повинні розуміти зв’язок між флагами і маніпуляторами.
Точність, запис чисел і десяткова крапка
Використовуючи маніпулятори (або флаги), можна змінити точність і формат виводу значень типу з плаваючою крапкою.
Флаги групи форматування floatfield:
fixed — використовується десятковий запис чисел типу з плаваючою крапкою;
scientific — використовується експоненціальний запис чисел типу з плаваючою крапкою;
showpoint — завжди відображається десяткова крапка і кінцеві нулі для чисел типу з плаваючою крапкою.
Маніпулятори:
fixed — використовується десятковий запис значень;
scientific — використовується експоненціальний запис значень;
showpoint — відображається десяткова крапка і кінцеві нулі чисел типу з плаваючою крапкою;
noshowpoint — не відображаються десяткова крапка і кінцеві нулі чисел типу з плаваючою крапкою;
setprecision(int) — вказуємо точність для чисел типу з плаваючою крапкою.
Методи:
precision() — повертаємо поточну точність для чисел типу з плаваючою крапкою;
precision(int) — вказуємо точність для чисел типу з плаваючою крапкою.
Якщо використовується десятковий або експоненціальний запис чисел, то точність визначає кількість цифр після коми/крапки. Зверніть увагу, якщо точність менше кількості значущих цифр, то число буде округлено. Наприклад:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include <iostream> #include <iomanip> // для setprecision() int main() { std::cout << std::fixed; 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+02
1.2346e+02
1.23456e+02
1.234560e+02
1.2345600e+02
Якщо не використовуються ні десятковий, ні експоненціальний запис чисел, то точність визначає, скільки значущих цифр відображатиметься. Наприклад:
1 2 3 4 5 6 7 8 9 10 11 |
#include <iostream> #include <iomanip> // для setprecision() int main() { 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, ми можемо змусити програму виводити десяткову крапку і кінцеві нулі. Наприклад:
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <iostream> #include <iomanip> // для setprecision() int main() { std::cout << std::showpoint; 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
Ширина поля, символи-заповнювачі і вирівнювання
Зазвичай числа виводяться без урахування простору навколо них. Проте, числа можна вирівнювати. Щоб це зробити, потрібно спочатку визначити ширину поля (тобто кількість простору (пробілів) навколо значень).
Флаги групи форматування adjustfield:
internal — знак значення вирівнюється по лівому краю, а саме значення — по правому краю;
left — значення і його знак вирівнюються по лівому краю;
right — значення і його знак вирівнюються по правому краю.
Маніпулятори:
internal — знак значення вирівнюється по лівому краю, а саме значення — по правому краю;
left — значення і його знак вирівнюються по лівому краю;
right — значення і його знак вирівнюються по правому краю;
setfill(char) — вказуємо символ-заповнювач;
setw(int) — вказуємо ширину поля.
Методи:
fill() — повертаємо поточний символ-заповнювач;
fill(char) — вказуємо новий символ-заповнювач;
width() — повертаємо поточну ширину поля;
width(int) — вказуємо ширину поля.
Щоб використовувати будь-який з вищеперерахованих об’єктів, потрібно спочатку встановити ширину поля. Це робиться за допомогою методу width(int)
або маніпулятора setw(). Зверніть увагу, за замовчуванням при використанні ширини поля значення вирівнюються по правому краю. Наприклад:
1 2 3 4 5 6 7 8 9 10 11 |
#include <iostream> #include <iomanip> // для setw() int main() { 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'; // знак значення вирівнюється по лівому краю, а саме значення - по правому } |
Результат:
1 2 3 4 5 |
-12345 -12345 -12345 -12345 - 12345 |
Тепер давайте задамо свій власний символ-заповнювач:
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <iostream> #include <iomanip> // для setw() int main() { 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
, розглянути їх усі в рамках даного уроку ми не можемо. Однак основний функціонал і загальне уявлення ви, сподіваюся, отримали.