Лекція № 17
Тема: Файли. Робота з файлами. Файловий ввід-вивід
План
Файловий ввід-вивід
Відкриття і закриття потоку
Читання і запис текстових файлів
Безформатне і бінарне введення-виведення
Файловий ввід-вивід
Не дивлячись на те що система введення-виведення мови C++ в цілому є єдиним механізмом, система файлового введення-виведення має свої особливості. Частково це пояснюється тим, що на практиці найчастіше використовуються файли на жорсткому диску, можливості яких значно відрізняються від всіх інших пристроїв. Проте слід мати на увазі, що файлове уведення-виведення є лише частиною загальної системи введення-виведення, і велика частина матеріалу, викладеного в розділі, відноситься до потоків, які можуть бути пов'язані з іншими пристроями.
Для реалізації файлового введення-виведення в програму слід включити заголовок <fstream>. У нім визначені деякі класи, зокрема, ifstream, ofstream і f stream. Ці класи є похідними від класів istream, ostream і ios-tream відповідно. Слід пам'ятати, що класи istream, ostream і iostream, у свою чергу, є похідними від класу ios, розглянутого в попередньому розділі. Файлова система використовує також клас filebuf, що надає низкоуровневые засоби управління файловим потоком. Зазвичай клас filebuf непосредственно не застосовується, проте він є складовою частиною інших класів.

Відкриття і закриття файлу
У мові C++ відкриття файлу означає його пов'язання з потоком. Отже, спочатку необхідно отримати потік. Існують три види потоків: введення, виведення і введення-виведення. Для того, щоб створити потік введення, необхідно оголосити потік, що є об'єктом класу ifstream Для генерації потоку виведення необхідно оголосити потік, що є об'єктом класу ofstream. Потоки, здійснююче введення і виведення, оголошуються як об'єкти класу fstream Наприклад, в наступному фрагменті створюється потік введення, потік виведення і потік введення-виведення.
ifstream in; // Введення
ofstream out; // Виведення
fstream io; // Введення і виведення
Створений потік можна пов'язати з файлом за допомогою функції ореn(). Ця функція є членом кожного з трьох потокових класів. Її прототипи в кожному класі показані нижче.
ifstream::open(const char *filename, ios::openmode
mode = ios::in);
ifstream: : open (const char * filename,
ios : : openmode mode = ios::out | ios::trunc);
ifstream::open(const char * filename,
ios::openmode mode = ios::in | ios::out);
Тут параметр filename задає ім'я файлу. Він може містити шлях до цього файлу. Значення параметра mode визначає спосіб відкриття файлу. Цей параметр може приймати наступні значення, описані у функції openmode.
ios::арр ios::ate ios::binary ios::in ios::out ios::trunc
Дані значення можна комбінувати за допомогою логічної операції "АБО". Якщо задається значення ios:: арр, всі результати дописуються в кінець файлу, що риється для виведення. Якщо вказано значення ios: :ate, при відкритті виконується позов кінця файлу. Не дивлячись на це, запис проводиться в будь-яке місце файлу. Якщо задається значення ios:: in, файл відкривається для введення, а якщо ios:: out — виведення.
Значення ios::binary дозволяє відкрити файл в бінарному режимі. По замовчуванню всі файли відкриваються в текстовому режимі. В цьому випадку проводиться перетворення деяких символів, наприклад, ескейп-послідовність "повернення каретки" і "прогін паперу" перетвориться в символ переходу на новий рядок. Проте, файл відкритий в бінарному режимі, перетворення символів не проводиться. Слід пам'ятати, що будь-який файл можна відкрити як в текстовому, так і в бінарному режимі. Єдина відмінність між ними полягає в тому, проводиться перетворення символів чи ні.
Значення ios::trunc сигналізує, що попередній вміст існуючого файлу з тим же ім'ям буде знищений, а довжина файлу зменшена до нуля. При відкритті потоку виведення за допомогою класу ofstream вміст будь-якого існуючого файлу з вказаним ім'ям стирається. В наступному фрагменті відкривається текстовий файл для виведення.

ofstream out; out.open( "test", ios::out);

Проте функція open() рідко застосовується для відкриття файлів, оскільки для кожного типу потоку параметр mode має значення, задані за умовчанням. Прототипи функції open демонструють, що значення параметра mode, задане по замовчуванню, в класі ifstream рівне ios::in, в класі ofstream – ios::out | ios: :trunc, а в класі fstream — ios: :in | ios: :out. По цій причині попередній виклик функції open зазвичай записують так:
out.open(" test"); // Текстовий файл для виведення
Якщо потік застосовується в логічних виразах, у разі відмови функції open() йому привласнюється значення false. Таким чином, перед використанням файлу слід переконатися, що він успішно відкритий. Для цього можна скористатися наступними операторами.
if(!mystream) {
cout « "Неможливо відкрити файл.\n";
// Обробка помилки
}
Однак частіше за все функцію ореn() не застосовують, оскільки класи ifstream, ofstream і fstream містять конструктори, що автоматично відкривають файл. Параметри цих конструкторів приймають ті ж значення за умовчанням, що і функція ореn(). З цієї причини файли зазвичай відкривають таким чином.
ifstream mystream( "myfile"); // Відкриття файлу для введення
Якщо з якої-небудь причини файл відкрити не вдалося, пов'язаному з ним потоку присвоюється значення false. Отже, якщо якийсь конструктор викликає функцію open(), слід переконатися, що файл дійсно відкритий, перевіривши значення потоку.
Щоб перевірити, чи відкритий файл, можна викликати функцію is_open(), яка є членом класу fstream, ifstream і ofstream. Вона має наступний прототип.
bool is_open();
Якщо потік пов'язаний з відкритим файлом, ця функція повертає значення true, інакше вона повертає значення false. Наприклад, наступний фрагмент перевіряє, чи відкритий файл, пов'язаний з потоком mystream.
if(!mystream.is_open()) {
cout « "Файл не відкритий.\n";
// ...
}
Щоб закрити файл, слід викликати функцію close(). Наприклад, щоб закрити файл, пов'язаний з потоком mystream, можна застосувати наступний оператор
mystream.close() ;
Функція close () не має параметрів і не повертає ніяких значень.
Читання і запис текстових файлів
Читання і запис текстових файлів здійснюються дуже легко. Для цього достатньо застосувати операторів "<<" і ">>", як це зазвичай робиться для консольного введення-виведення, тільки замість потоків cin і cout необхідно підставити потік, що зв’язаний з файлом. Наприклад, наступна програма створює короткий файл, який містить назву предмету і його вартість.
#include <iostream>
#include <fstream>
using namespace std;
int main() {
ofstream out("INVNTRY"); // Текстовий файл для виведення.
if(!out){
cout « "Неможливо відкрити файл INVENTORY.\n";
return 1;
}
out << "Радіоприймачі " « 39.95 « endl;
out « "Тостери " « 19.95 « endl;
out « "Міксери " « 24.80 « endl;
out.close();
return 0;
}
Програма, приведена нижче, прочитує файл, створений попередньою програмою, і виводить його вміст на екран.
#include <iostream>
#include <fstream>
using namespace std;
int main () {
ifstream in("INVNTRY"); // Введення
if(!in) {
cout << "Неможливо відкрити файл INVENTORY. \n" ;
return 1;
}
char item[20];
float cost;
in » item » cost;
cout « item « " " « cost « "\n"
in » item >> cost;
cout « item « " " « cost « "\n"
in >> item » cost;
cout « item « " " « cost « "\n"
in.close() ;
return 0;
}

Читання і запис файлів за допомогою операторів "«" і "»" нагадує використання функцій fprintf() і fscanf() з мови С. Формат записів у файлі нічим не відрізняється від формату виведення на екран.
Прочитуючи файли за допомогою оператора ">>", майте на увазі, що деякі символи при введенні трансформуються. Наприклад, роздільники ігноруються. Щоб відмінити перетворення символів при читанні, файл слід відкрити в бинарному режимі і застосувати функції, описані в наступному розділі.
Якщо при введенні досягається кінець файлу, потоку, пов'язаному з файлом, присвоюється значення false. (Ця ситуація ілюструється в наступному розділі.)

Безформатне і бінарне уведення-виведення
Отже, читання і запис форматованих текстових файлів не викликає ніяких труднощів, хоча це не найефективніший спосіб роботи з файлами. Окрім цього іноді виникає необхідність зберігати безформатні дані, а не текст. Розглянемо функції, призначені для роботи з такими даними.
Виконуючи з файлом бінарні операції, слід переконатися, що він відкритий в режимі ios::binary. Безформатні дані можуть зберігатися і в текстовому файлі, але в цьому випадку при читанні деякі символи будуть перетворені. Бінарні файлу застосовуються саме для того, щоб цього уникнути.
Порівняння символів і байтів
При вивченні безформатного введення-виведення необхідно враховувати наступне. Багато років уведення-виведення в мовах С і C++ був байтовим (byte oriented). Це відбувалося тому, що символ (char) є еквівалентом байта, і потоки виведення були символьними. Проте з появою розширених символів (wchar_t і пов'язаних з ними потоків систему введення-виведення мови C++ не можна назвати байтовою. Тепер її слід називати символьною (character oriented). Зрозуміло, потоки звичайних символів (char) залишаються байтовими, особливо при обробці нетекстових даних. Проте еквівалентність понять "символ" і "байт" більше не гарантуєте
Ми використовуємо тільки символьні (тобто байтові) потоки, оскільки вони найширше поширені. Вони забезпечують простішу обробку безформатних файлів, оскільки символьні потоки збергіають однозначну відповідність між символами і байтами.
Функції put() і get()
Один із способів читання і запису безформатних файлів заснований на використанні функцій put () і get (). Ці функції оперують символами. Точніше кажучи, функція get () прочитує символ, а функція put () — записує його. Зрозуміло, якщо файл відкритий в бінарному режимі, то при прочитуванні символу (а не розширення символу), ці функції прочитують і записують байти.
Функція get () має декілька форм, проте найчастіше використовується її наступна версія.
istream &get(char &ch)
ostream &put(char ch)
Функція get () прочитує окремий символ з потоку і записує його в змінну ch. Крім того, функція get () повертає посилання на потік. Функція put() записує змінну ch в потік і повертає посилання на потік
Наступна програма відображає на екрані вміст будь-якого файлу (як текстового, так і бінарного). Для зчитування даних вона використовує функцію get ().
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char *argv[])
{
char ch;
if(argc!=2){
cout << "виклик: PR <ім'я файла>\n";
return 1;
}
ifstream in(argv[l], ios::in | ios::binary);
if(!in){
cout « "Неможливо відкрити файл.";
return 1;
}
while(in) { // Якщо досягнутий кінець файлу
// значення об'єкту in рівне false.
in.get(ch);
if(in) cout « ch;
}
return 0;
}
Як указувалося в попередньому розділі, після досягнення кінця файлу потік, связанный з цим файлом, приймає значення false. Отже, рано чи пізно об'єкт in прийме значення false, і виконання циклу while припиниться.
Приведений вище цикл можна записати коротше.
while(in.get(ch)) cout « ch;
Цей фрагмент є правильним, оскільки функція get () повертає ссылку на потік in, який прийме значення false при виявленні кінця файлу.
Функції read() і write()
Блоки бінарних даних можна прочитувати за допомогою функцій read() i write(). Їх прототипи виглядають таким чином.
istream &read(char *buf, streamsize num);
istream &write(const char *buf, streamsize num);
Функція read прочитує num символів з потоку і записує їх в буфер, на який посилається покажчик buf. Функція write записує num символів в потік прочитуючи їх з буфера, на який посилається покажчик buf. Тип streamsize визначений в бібліотеці як різновид типу int. Він дозволяє зберігати максимальну кількість символів, які можуть перетворюватися при виконанні операцій введення-виведення.