Лабораторна робота № 2
Потоковий ввід-вивід
Мета роботи: познайомитися із потоковим вводом-виводом.
Короткі теоретичні відомості
Потоки вводу-виводу в С++
Система вводу-виводу в стандартній бібліотеці С++ реалізована у вигляді потоків. Потік вводу-виводу – це логічний пристрій, який приймає та видає інформацію користувача. Кожен потік зв’язаний з фізичним пристроєм (клавіатура, монітор) або з файлом. Бібліотека потоків iostream реалізована як ієрархія класів та забезпечує широкі можливості для виконання операцій вводу-виводу. Далі наведено призначення деяких класів потокового вводу-виводу:
istream – підтримує операції по вводу;
ostream – підтримує операції по виводу;
iostream – підтримує операції по вводу-виводу;
іfstream – підтримує операції по вводу з файлу;
ofstream – підтримує операції по виводу у файл;
fstream – підтримує операції з файлами по вводу-виводу.
Стандартні потоки
Коли запускається програма на С++, автоматично створюються чотири стандартних потоки.
Табл..1
Потік
Призначення
Пристрій по замовчуванню

cin
Стандартний ввід
Клавіатура

cout
Стандартний вивід
Монітор

cerr
Стандартний вивід повідомлень про помилки
Монітор

clog
Стандартний вивід повідомлень про помилки (буферизований)
Монітор


Щоб мати можливість використовувати стандартні потоки необхідно підключити заголовочний файл iostream.h.
Об’єкт стандартного потоку вводу cin класу istream, зв’язаний із стандартним пристроєм вводу, за звичай клавіатурою. Об’єкт стандартного потоку виводу cout класу ostream, зв’язаний із стандартним пристроєм виводу, за звичай монітором. Об’єкт cerr класу ostream, зв’язаний із стандартним пристроєм виводу повідомлень про помилки. Потоки даних, що виводяться, для об’єкту cerr являються небуферизованими. Тобто кожна операція помістити в cerr приводить до миттєвої появи повідомлень про помилки. Об’єкт clog класу ostream, зв’язаний із стандартним пристроєм виводу повідомлень про помилки. Потоки даних, що виводяться, для об’єкту clog являються буферизованими. Тобто кожна операція помістити в clog може привести до того, що вивід буде зберігатися в буфері до тих пір, поки буфер повністю не заповниться або ж поки вмістиме буферу не буде виведене примусово.
Вивід в потік виконується за допомогою операції «помістити в потік», а саме перевантаженої операції <<. Дана операція перевантажена для виводу елементів даних стандартних типів, для виводу рядків та значень вказівників.
Операція << повертає посилання на об'єкт типу ostream, для якого вона викликана. Це дозволяє будувати ланцюжок викликів операції «помістити в потік», що виконуються зліва направо.
іnt і = 5;
double d = 2.08;
cout << "і = " << і << ", d = " << d << '\n';
Ці оператори приведуть до виведення на екран наступного рядка:
і = 5, d = 2.08


Ввід потоку виконується за допомогою операції «взяти із потоку», а саме перевантаженої операції >>. Дана операція зазвичай ігнорує у вхідному потоці символи розділювачі та пробільні символи. Операція «взяти із потоку» повертає нульове значення (false), якщо зустрічає в потоці признак кінця файлу або виникає помилка при спробі читання із потоку.
Засоби форматування потоку
Система вводу-виводу дозволяє виконувати форматування даних та змінювати визначені параметри вводу інформації. Дані операції реалізовані за допомогою функцій форматування, прапорців та маніпуляторів.
Функції форматування та їх призначення приведені у табл.2.
Табл..2
Функція-член
Призначення

width(int wide)
Дозволяє задати мінімальну ширину поля для виведення значення. При вводі задає максимальне число символів, що читаються. Якщо значення, що виводиться, має менше символів, чим задана ширина поля, то воно доповнюється символами-заповнювачами до заданої ширини (за замовчуванням - пробілами). Якщо ж значення, що виводиться має більше символів, чим ширина відведеного йому поля, то поле буде розширене.

precision(int prec)
Дозволяє прочитати або встановити точність (число цифр після десяткової крапки), з якою виводяться числа з плаваючою крапкою. По замовчуванню числа з плаваючою крапкою виводяться з точністю, рівною шести цифрам.

fill(char ch)
Дозволяє прочитати або встановити символ-заповнювач.


void main()
{
double x;
cout.precision(4);
cout.fill('0');
cout << " x sqrt(x)\n";
for (x = 1.0; x <= 6.0; x++)
{
cout.width(7);
cout << x << ' ';
cout.width(7);
cout << sqrt(x) << '\n';
}
}
Результат роботи програми наступний:
x sqrt(x)
0000001 0000001
0000002 001.414
0000003 001.732
0000004 0000002
0000005 002.236
0000006 002.449


З кожним потоком зв'язаний набір прапорців, що керують форматуванням потоку. Вони являють собою бітові маски. Прапорці форматування і їхнє призначення приведені в табл.3. Встановити значення одного або декількох прапорців можна за допомогою функції-члену setf(long mask).
Табл..3
Прапорець
Призначення

dec
Встановлюється десяткова система числення

hex
Встановлюється шістнадцяткова система числення

oct
Встановлюється вісімкова система числення

scientific
Числа з плаваючою крапкою, виводяться в науковому записі (тобто n.хххЕуу)

showbase
Виводиться основа системи числення у виді префікса до цілого числового значення (наприклад, число 1FE виводиться як 0x1FE)

showpos
При виводі позитивних числових значень виводиться знак плюс

uppercase
Замінюються визначені символи нижнього регістра на символи верхнього регістра (символ "е" при виведенні чисел в науковому записі – на "Е" і символ "х" при виведенні чисел в шістнадцятковій системі – на "X")

left
Дані при виведенні вирівнюються по лівому краю поля виводу

right
Дані при виведенні вирівнюються по правому краю поля виводу

internal
Додаються символи-заповнювачі між усіма цифрами і знаками числа для заповнення поля виводу

skipws
Ведучі символи-заповнювачі (знаки пробілу, табуляції і переходу на новий рядок) відкидаються


void main()
{
double d = 3.124e7;
int n = 25;

cout << "d = " << d << '\n' ;
cout << "n = " << n << '\n';

cout.setf(ios::hex | ios::uppercase);
cout.setf(ios::showpos);
cout << "d = " << d << '\n' ;
cout << "n = " << n << '\n';
}
Результат роботи програми наступний:
d = 3.124e+007
n = 25
d = +3.124E+007
n = 19


Список маніпуляторів та їхнє призначення приведені в табл.4. Маніпулятори вводу-виводу являють собою вид функцій-членів класу іos, що, на відміну від звичайних функцій-членів, можуть розташовуватися усередині операцій вводу-виводу.
За винятком setw( ), усі зміни в потоці, внесені маніпулятором, зберігаються до наступної установки.
Для доступу до маніпуляторів з параметрами необхідно включити в програму стандартний заголовний файл іomanіp.h.
Табл..4
Маніпулятор
Призначення

endl
Виводить символ нового рядка та очищує потік

flush
Видає вмістиме буфера потоку у пристрій

dec
Встановлює десяткову систему числення

hex
Встановлює шістнадцяткову систему числення

oct
Встановлює вісімкову систему числення

setbase (int base)
Задає основу системи числення для цілих чисел (8,10,16)

setfill (int c)
Встановлює символ-заповнювач

setprecision (int n)
Встановлює точність чисел з плаваючою крапкою

setw(int n)
Встановлює мінімальну ширину поля виводу

setiosflags (iosbase::long mask)
Встановлює ios-прапорці згідно з mask


void main()
{
double x = 45.12345;
cout << "x = " << setprecision(4)
<< setfill('0') << setw(7) << x << endl;
}
Результат роботи програми наступний:
x = 0045.12


Часто застосовувані функції
Крім вже описаних функцій, бібліотека вводу-виводу C++ містить широкий набір різних функцій. Тут ми приведемо лише деякі, найбільш часто вживані.
Для читання символу з потоку можна використовувати функцію-член get() потоку іstream. Функція get() повертає код прочитаного символу або -1, якщо зустрівся кінець файлу вводу (ctrl/z).
Функція get(char* str, іnt len, char delіm) може також використовуватися для читання рядка символів. У цьому випадку використовуються її варіант, у якому ця функція читає з вхідного потоку символи в буфер str, поки не зустрінеться символ-обмежувач delіm (за замовчуванням – \n) або не буде прочитано (len-1) символів чи ознаку кінця файлу. Сам символ-обмежувач не читається з вхідного потоку.
Для вставки символу в потік виведення використовується функція put( ).
Через те, що функція get() не читає з вхідного потоку символ-обмежувач, вона використовується рідко. Набагато частіше використовується функція getlіne(char* str, іnt len, char delіm), що читає з вхідного потоку символ-обмежувач, але не поміщає його в буфер.
Функція gcount() повертає число символів, прочитаних з потоку останньою операцією неформатуючого вводу (тобто функцією get(), getlіne() або read()).
Розглянемо приклад, у якому використовуються дві останні функції:
void main(void)
{
const len = 100;
char name[len];
int count = 0;

cout << "Enter your name" << endl;
cin.getline(name, len);
count = cin.gcount();
// Зменшуємо значення лічильника на 1, тому що
// getlіne() не поміщає обмежувач в буфер
cout << "number of symbols is " << count - 1 << endl;
}
Результат роботи програми наступний:
Enter your name
Petro
number of symbols is 5


Для того, щоб пропустити при введенні кілька символів, використовується функція іgnore(іnt n = l, іnt delіm = EOF). Ця функція ігнорує n символів у вхідному потоці. Пропуск символів припиняється, якщо вона зустрічає символ-обмежувач, яким по замовчуванню є символом кінця файлу. Символ-обмежувач читається з вхідного потоку.
Функція peek( ) дозволяє "заглянути" у вхідний потік і довідатися наступний символ, що вводиться. При цьому сам символ з потоку не читається.
За допомогою функції putback(char ch) можна повернути символ ch у потік вводу.
Файловий ввід-вивід
Для виконання операцій з файлами передбачено три класи: іfstream, ofstream і fstream. Ці класи є похідними від класів іstream, ostream і іostream. Всі функціональні можливості (перевантажені операції << та >> для вбудованих типів, функції і прапорці форматування, маніпулятори й ін.), що застосовуються до стандартного вводу та виводу, можуть застосовуватися і до файлів. Існує деяка відмінність між використанням стандартних та файлових потоків. Стандартні потоки можуть використовуватися відразу після запуску програми, тоді як файловий потік спочатку слід зв'язати з файлом. Для реалізації файлового вводу-виводу потрібно підключити заголовочний файл fstream.h.
Відкрити файл для вводу чи виводу можна наступним чином:
// Для виводу
ofstream outfile;
outfile.open("File.txt");
або
ofstream outfile("File.txt");
або
fstream outfile("File.txt ",ios::out);
// Для вводу
ifstream infile;
infile.open("File.txt");
або
ifstream infile("File.txt");
або
fstream infile("File.txt ",ios::in);


Режими відкриття файлів та їхнє призначення наведені у табл.5.
Табл..5
Режим відкриття
Призначення

іos::іn
Відкрити файл для читання

іos::out
Відкрити файл для запису

іos::ate
Відкрити файл для додавання в кінець

іos::app
Відкрити файл для додавання в кінець

іos::trunc
Усікти файл, тобто видалити його вміст

іos::bіnary
Відкрити файл у двійковому режимі


Режими відкриття файлу являють собою бітові маски, тому можна задавати два або більш режими, поєднуючи їх побітовою операцією АБО. Слід звернути увагу, що по замовчуванню режим відкриття файлу відповідає типові файлового потоку. У потоці вводу або виводу прапорець режиму завжди встановлений неявно.
Між режимами відкриття файлу іos::ate та іos::app існує певна відмінність. Якщо файл відкривається в режимі додавання, весь вивід у файл буде здійснюватися в позицію, що починається з поточного кінця файлу, безвідносно до операцій позиціонування у файлі. У режимі відкриття іos::ate (від англійського "at end") можна змінити позицію виводу у файл і здійснювати запис, починаючи з неї. Файли, які відкриваються для виводу, створюються, якщо вони ще не існують.
Якщо при відкритті файлу не зазначений режим іos::bіnary, файл відкривається в текстовому режимі.
Якщо відкриття файлу завершилося невдачею, об'єкт, що відповідає потокові, буде повертати нуль. Перевірити успішність відкриття файлу можна також за допомогою функції-члена іs_open(). Дана функція повертає 1, якщо потік вдалося зв'язати з відкритим файлом.
Для перевірки, чи досягнутий кінець файлу, можна використовувати функцію eof(). Завершивши операції вводу-виводу, необхідно закрити файл, викликавши функцію-член close().
Далі наведений приклад, що демонструє файловий ввід-вивід з використанням потоків.
#іnclude < fstream.h >
int maіn( )
{
іnt n = 50;
ofstream ofile("Test.txt"); // Відкриваємо файл для виводу
іf ( !ofile)
{
cout << "Файл не відкритий. \n";
return -1;
}
ofile << "Hello!\n" << n;
ofile.close(); // Закриваємо файл
іfstream ifіle("Test.txt"); // Відкриваємо той же файл для вводу
іf ( !ifіle )
{
cout << "Файл не відкритий.\n";
return -1;
}
char str[80];
ifіle >> str >> n;
cout << str << " " << n << endl;
ifіle.close(); // Закриваємо файл
return 0;
}


Завдання
Написати програму, яка буде додавати у текстовий файл введену з клавіатури інформацію (згідно варіанту). Слід передбачити можливість вибору користувачем режиму роботи: додавання чи відображення даних. Забезпечити зберігання даних у файлі у вигляді структурованої таблиці, для цього слід використовувати засоби форматування.
Варіант
Завдання

1
З клавіатури вводиться прізвище та номер телефону

2
З клавіатури вводиться ціле число, у файл записується його 10-ткове, 16-ткове та 8-кове значення

3
З клавіатури вводиться ім’я компанії, рік заснування

4
З клавіатури вводиться ім’я та день народження (день та місяць)

5
З клавіатури вводиться ціле число, у файл записується саме число та значення квадратного кореня з даного числа

6
З клавіатури вводиться число з плаваючою крапкою, у файл записується саме число та його заокруглене значення

7
З клавіатури вводиться назва товару та його ціна