Namespace CounterNameSpace 12 страница

Рекомендщується також перевірити факт успішного відкриття файлу за допомогою функції is_open(), яка є членом класів fstream, ifstream і ofstream. Ось її прототип:

bool is_open();

Ця функція повертає значення ІСТИНА, якщо потік пов'язаний з відкритим файлом, і ФАЛЬШ – в іншому випадку. Наприклад, використовуючи такий фрагмент коду програми, можна дізнатися про те, чи відкрито у даний момент потоковий об'єкт myStream:

if(!myStream.is_open()) {

cout << "Файл не відкрито.\n";

//. . .

}

Хоча цілком коректно використовувати функцію open() для відкриття файлу, проте здебільшого це робиться по-іншому, оскільки класи ifstream, ofstream і fstream містять конструктори, які автоматично відкривають заданий файл. Параметри у цих конструкторів і їх значення (що діють за замовчуванням) збігаються з параметрами і відповідними значеннями функції open(). Тому найчастіше файл відкривається так, як це показано в наведеному нижче прикладі:

ifstream myStream("myFile"); // Файл відкривається для введення

Якщо з деякої причини файл відкрити неможливо, то потоковій змінній, що пов'язується з цим файлом, встановлюється значення, що дорівнює ФАЛЬШ.

Щоб закрити файл, використовується функція close().

Щоб закрити файл, використовується функція-член close(). Наприклад, щоб закрити файл, який є пов'язаним з потоковим об'єктом myStream, потрібно використати таку настанову:

myStream.close();

Функція close() не має параметрів і не повертає ніякого значення.

19.5.2. Зчитування і запис текстових файлів

Найпростіше зчитувати дані з текстового файлу або записувати їх до нього за допомогою операторів "<<" і ">>". Наприклад, у наведеному нижче коді програми виконується запис у файл test цілого числа, значення з плинною крапкою і рядка.

Код програми 19.11. Демонстрація запису даних у файл

#include <iostream>// Для потокового введення-виведення

#include <fstream> // Для роботи з файлами

using namespace std; // Використання стандартного простору імен

 

int main()

{

ofstream out("test");

if(!out) {

cout << "Не вдається відкрити файл.\n";

return 1;

}

 

out << 10 << " " << 123.23 << "\n";

out << "Це короткий текстовий файл.";

 

out.close();

 

getch(); return 0;

}

Наведений нижче код програми зчитує ціле число, float-значення, символ і рядок з файлу, створеного у процесі виконання попередньою програмою:

Код програми 19.12. Демонстрація зчитування даних з файлу

#include <iostream>// Для потокового введення-виведення

#include <fstream> // Для роботи з файлами

using namespace std; // Використання стандартного простору імен

 

int main()

{

char ch;

int izm;

float f;

char strMas[80];

 

ifstream in("test");

if(!in) {

cout << "Не вдається відкрити файл.\n";

return 1;

}

 

in >> izm;

in >> f;

in >> ch;

in >> strMas;

 

cout << izm << " " << f << " " << ch << "\n";

cout << strMas;

 

in.close();

 

getch(); return 0;

}

Варто знати! Під час використання перевантаженого оператора ">>" для зчитування даних з текстових файлів відбувається перетворення деяких символів. Наприклад, "пропускні" символи опускаються. Якщо необхідно запобігти будь-яким перетворенням символів, то потрібно відкрити файл у двійковому режимі доступу до його даних.

Необхідно пам'ятати! Під час використання перевантаженого оператора ">>" для зчитування рядка введення даних припиняється внаслідок виявлення першого "пропускного" символу.

19.5.3. Неформатоване введення-виведення даних у двійковому режимі

Форматовані текстові файли (подібні тим, які використовувалися у попередніх прикладах) корисні в багатьох ситуаціях, але вони не мають гнучкості неформатованих двійкових файлів. Тому мова програмування C++ підтримує ряд функцій файлового введення-виведення у двійковому режимі, які можуть виконувати операції без форматування даних.

Для виконання двійкових операцій файлового введення-виведення необхідно відкрити файл з використанням специфікатора режиму ios::binary. Необхідно відзначити, що функції, які використовуються для оброблення неформатованих файлів, можуть також виконувати дії з файлами, відкритими в текстовому режимі доступу, але при цьому використовується перетворення символів, яке зводить нанівець основну мету виконання двійкових файлових операцій.

Функція get() зчитує символ з файлу, а функція put() записує символ у файл.

У загальному випадку існує два способи запису неформатованих двійкових даних у файл і зчитування їх з файлу. Перший спосіб полягає у використанні функції-члена класу put() (для запису байта у файл) і функції-члена класу get() (для зчитування байта з файлу). Другий спосіб передбачає застосування "блокових" С++-функцій введення-виведення read() і write(). Розглянемо кожен спосіб окремо.

Функції get() і put() мають багато форматів, але найчастіше використовуються такі їх версії:

istream &get(char &ch);

ostream &put(char ch);

1. Функція get() зчитує один символ з відповідного потоку і поміщає його значення у змінну ch. Вона повертає посилання на потік, що є пов'язаним із заздалегідь відкритим файлом. Досягнувши кінця цього файлу, значення посилання дорівнюватиме нулю.

2. Функція put() записує символ ch у потік і повертає посилання на цей потік.

У процесі виконання наведеної нижче програми на екран буде виведено вміст будь-якого заданого файлу. Тут використовується функція get().

Код програми 19.13. Демонстрація відображення вмісту файлу за допомогою функції get()

#include <iostream>// Для потокового введення-виведення

#include <fstream> // Для роботи з файлами

using namespace std; // Використання стандартного простору імен

 

int main(int argc, char *argv[])

{

char ch;

 

if(argc !=2) {

cout << "Застосування: ім'я_програми <ім'я_файлу>\n";

return 1;

}

 

ifstream in(argv[1], ios::in | ios::binary);

if(!in) {

cout << "Не вдається відкрити файл.\n";

return 1;

}

 

while(in) { // Досягши кінця файлу потоковий

// об'єкт in прийме значення false.

in.get(ch);

if(in) cout << ch;

}

 

in.close();

 

getch(); return 0;

}

Досягши кінця файлу, потоковий об'єкт in прийме значення ФАЛЬШ, яке зупинить виконання циклу while. Проте існує дещо коротший варіант коду програми організації циклу, призначеного для зчитування і відображення вмісту файлу:

while(in.get(ch)) cout << ch;

Цей варіант організації циклу також має право на існування, оскільки функція get() повертає потоковий об'єкт in, який, досягши кінця файлу, прийме значення false.

У наведеному нижче коді програми для запису рядка у файл використовується функція put().

Код програми 19.14. Демонстрація механізму використання функції put() для запису рядка у файл

#include <iostream>// Для потокового введення-виведення

#include <fstream> // Для роботи з файлами

using namespace std; // Використання стандартного простору імен

 

int main()

{

char *p = "Всім привіт!";

 

ofstream out("test", ios::out | ios::binary);

 

if(!out) {

cout << "Не вдається відкрити файл.\n";

return 1;

}

while(*p) out.put(*p++);

 

out.close();

 

getch(); return 0;

}

19.5.4. Зчитування і запис у файл блоків даних

Для зчитування і записування у файл блоків двійкових даних використовуються функції-члени read() і write(). Їх прототипи мають такий вигляд:

istream &read(char *buf, streamsize num|);

 

ostream &write(const char *buf, int streamsize num|);

1. Функція read() зчитує num байт даних з пов'язаного з файлом потоку і поміщає їх у буфер, яка адресується покажчиком buf.

2. Функція write() записує num байт даних у пов'язаний з файлом потік з буфера, яка адресується покажчиком buf.

Як ми вже зазначали вище, тип streamsize визначається як деякий різновид цілочисельного типу. Він дає змогу зберігати найбільшу кількість байтів, яка може бути передана у процесі будь-якої операції введення-виведення даних.

Функція read() вводить блок даних, а функція write() виводить його.

У процесі виконання наведеної нижче програми спочатку у файл записується масив цілих чисел, а потім його значення зчитується з файлу.

Код програми 19.15. Демонстрація механізму використання функцій read() і write()

#include <iostream>// Для потокового введення-виведення

#include <fstream> // Для роботи з файлами

using namespace std; // Використання стандартного простору імен

 

int main()

{

int n[5] = {1, 2, 3, 4, 5};

register int i;

 

ofstream out("test", ios::out | ios::binary);

if(!out) {

cout << "Не вдається відкрити файл.\n";

return 1;

}

 

out.write((char *) &n, sizeof n);

 

out.close();

for(i=0; i<5; i++) n[i] = 0; // Очищtyyz масивe

 

ifstream in("test", ios::in | ios::binary);

if(!in) {

cout << "Не вдається відкрити файл.\n";

return 1;

}

 

in.read((char *) &n, sizeof n);

for(i=0; i<5; i++) // Відображаємо значення, зчитані з файлу.

cout << n[i] << " ";

 

in.close();

 

getch(); return 0;

}

Звернемо Вашу увагу на те, що в настановах звернення до функцій read() і write() виконуються операції приведення типу, які є обов'язковими під час використання буфера, що визначається у вигляді не символьного масиву.

Функція gcount() повертає кількість символів, зчитаних у процесі виконання останньої операції введення даних.

Якщо кінець файлу досягнуто ще до того моменту, як було зчитано num символів, то функція read() просто припинить своє виконання, а буфер міститиме стільки символів, скільки вдалося зчитати до цього моменту. Точну кількість зчитаних символів можна дізнатися за допомогою ще однієї функції-члена класу gcount(), яка має такий прототип:

streamsize gcount();

Функція gcount() повертає кількість символів, зчитаних у процесі виконання останньої операції введення даних.

19.5.5. Виявлення кінця файлу

Виявити кінець файлу можна за допомогою функції-члена класу eof(), яка має такий прототип:

bool eof();

Ця функція повертає значення true у випадку досягнення кінця файлу; інакше вона повертає значення false.

Функція eof() дає змогу виявити кінець файлу.

У наведеному нижче коді програми для виведення на екран вмісту файлу використовується функція eof().

Код програми 19.16. Демонстрація виявлення кінця файлу за допомогою функції eof()

#include <iostream>// Для потокового введення-виведення

#include <fstream> // Для роботи з файлами

using namespace std; // Використання стандартного простору імен

 

int main(int argc, char *argv[])

{

char ch;

if(argc != 2) {

cout << "Застосування: ім'я_програми <ім'я_файлу>\n";

return 1;

}

 

ifstream in(argv[1], ios::in | ios::binary);

if(!in) {

cout << "Не вдається відкрити файл.\n";

return 1;

}

 

while(!in.eof()) { // Використання функції eof()

in.get(ch);

if(!in.eof()) cout << ch;

}

 

in.close();

 

getch(); return 0;

}

19.5.6. Приклад порівняння файлів

Наведений нижче код програми ілюструє потужність і простоту застосування у мові програмування C++ файлової системи. Тут порівнюються два файли за допомогою функцій двійкового введення-виведення read(), eof() і gcount(). Програма спочатку відкриває порівнювані файли для виконання двійкових операцій (щоб не допустити перетворення символів). Потім з кожного файлу по черзі зчитуються блоки інформації у відповідні буфери і порівнюється їх вміст. Оскільки об'єм зчитаних даних може бути меншим за розмір буфера, то у програмі використовується функція gcount(), яка точно визначає кількість зчитаних у буфер байтів. Неважко переконатися у тому, що під час використання файлових С++-функцій для виконання цих операцій була потрібна зовсім невелика за розміром програма.

Код програми 19.17. Демонстрація механізму застосування файлової системи для порівняння файлів

#include <iostream>// Для потокового введення-виведення

#include <fstream> // Для роботи з файлами

using namespace std; // Використання стандартного простору імен

 

int main(int argc, char *argv[])

{

register int i;

 

unsigned char buf1[1024], buf2[1024];

if(argc !=3) {

cout << "Застосування: ім'я_програми <ім'я_файла1> "

<< "<ім'я_файла2>\n";

return 1;

}

 

ifstream f1(argv[1], ios::in | ios::binary);

if(!f1) {

cout << "Не вдається відкрити перший файл.\n";

return 1;

}

 

ifstream f2(argv[2], ios::in | ios::binary);

if(!f2) {

cout << "Не вдається відкрити другий файл.\n";

return 1;

}

 

cout << "Порівняння файлів.\n";

do {

f1.read((char *) buf1, sizeof buf1);

f2.read((char *) buf2, sizeof buf2);

if(f1.gcount() != f2.gcount()) {

cout << "Файли мають різні розміри.\n";

f1.close();

f2.close();

getch(); return0;

}

 

// Порівняння вмісту буферів.

for(i=0; i<f1.gcount(); i++)

if(buf1[i] != buf2[i]) {

cout << "Файли різні.\n";

f1.close();

f2.close();

getch(); return0;

}

} while(!f1.eof() && !f2.eof());

 

cout << "Файли однакові.\n";

 

f1.close();

f2.close();

 

getch(); return 0;

}

Проведіть експеримент. Розмір буфера у цій програмі жорстко встановлено таким, що дорівнює 1024. Як вправу замініть це значення const-змінної та випробуйте інші розміри буферів. Визначте оптимальний розмір буфера для свого операційного середовища.

19.5.7. Використання інших функцій для двійкового введення-виведення

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

istream &get(char *buf, streamsize num);

 

istream &get(char *buf, streamsize num, char delim);

int get();

1. Перша версія функції get() дає змогу зчитувати символи і заносити їх у масив, що задається параметром buf, доти, доки не буде зчитано num-1| символів, або не трапиться символ нового рядка, або не буде досягнуто кінець файлу. Після завершення роботи функції get() масив, яка адресується покажчиком buf, матиме завершальний нуль-символ. Символ нового рядка, якщо такий виявиться у вхідному потоці, не вилучається. Він залишається там доти, доки не виконається наступна операція введення-виведення.

2. Друга версія функції get() призначена для зчитування символів і занесення їх у масив, яка адресується покажчиком buf, доти, доки не буде зчитано num-1 символів, або не виявиться символ, який задається параметром delim, або не буде досягнуто кінець файлу. Після завершення роботи функції get() масив, яка адресується покажчиком buf, матиме завершальний нуль-символ. Символ-роздільник (заданий параметром delim) , якщо такий виявиться у вхідному потоці, не вилучається. Він залишається там доти, доки не виконається наступна операція введення-виведення.

3. Третя перевантажена версія функції get() повертає з потоку наступний символ. Він міститься в молодшому байті значення, що повертається функцією. Отже, значення, що повертається функцією get(), можна присвоїти змінній типу char. Досягши кінця файлу, ця функція повертає значення EOF, яке визначено у заголовку <iostream>.

Функцію get() корисно використовувати для зчитування рядків, що містять пропуски. Як уже зазначалося вище, якщо для зчитування рядка використовують оператор ">>", то процес введення даних зупиняється внаслідок виявлення першого ж пропускного символу. Це робить оператор ">>" даремним для зчитування рядків, що містять пропуски. Але цю проблему, як це показано в такій програмі, можна обійти за допомогою функції get(buf, num).

Код програми 19.18. Демонстрація механізму використання функції get() для зчитування рядків, що містять пропуски

#include <iostream>// Для потокового введення-виведення

#include <fstream> // Для роботи з файлами

using namespace std; // Використання стандартного простору імен

 

int main()

{

char strMas[80];

cout << "Введіть ім'я: ";

cin.get(strMas, 79);

cout << strMas << "\n";

 

getch(); return 0;

}

У цій програмі як символ-роздільник під час зчитування рядка за допомогою функції get() використовується символ нового рядка. Це робить поведінку функції get() багато в чому схожою з поведінкою стандартної функції gets(). Проте перевага функції get() полягає у тому, що вона дає змогу запобігти можливому виходу за межі масиву, який приймає символи, що вводяться користувачем, оскільки у програмі оголошено максимальну кількість зчитаних символів. Це робить функцію get() набагато безпечнішою за функцію gets().

Розглянемо ще одну функцію, яка дає змогу вводити дані. Йдеться про функцію getline(), яка є членом кожного потокового класу, призначеного для введення інформації. Ось як виглядають прототипи версій цієї функції:

istream &getline(char *buf, streamsize num);

 

istream &getline(char *buf, streamsize num, char delim);

Функція getline() використовується як ще один спосіб введення даних.

1. Під час використання першої версії функції getline()символи зчитуються і заносяться у масив, яка адресується покажчиком buf, доти, доки не буде зчитано num-1| символів, або не трапиться символ нового рядка, або не буде досягнуто кінець файлу. Після завершення роботи функції getline() масив, яка адресується покажчиком buf, матиме завершальний нуль-символ. Символ нового рядка, якщо такий виявиться у вхідному потоці, при цьому вилучається, але не заноситься у масив buf.

2. Друга версія функції getline()призначена для зчитування символів і занесення їх у масив, яка адресується покажчиком buf, доти, доки не буде зчитано num-1 символів, або не виявиться символ, який задається параметром delim, або не буде досягнуто кінець файлу. Після завершення роботи функції getline() масив, яка адресується покажчиком buf, матиме завершальний нуль-символ. Символ-роздільник (які задаються параметром delim) , якщо такий виявиться у вхідному потоці, вилучається, але не заноситься у масив buf.

Як бачите, ці дві версії функцій getline() практично ідентичні версіям get(buf, num) і get(buf, num, delim) функції get(). Обидві зчитують символи з вхідного потоку і заносять їх у масив, яка адресується покажчиком buf, доти, доки не буде зчитано num-1 символів, або не виявиться символ, який задається параметром delim. Відмінність між функціями get() і getline() полягає у тому, що функція getline() зчитує і видаляє символ-роздільник з вхідного потоку, а функція get() цього не робить.

Функція peek() зчитує наступний символ з вхідного потоку, не видаляючи його.

Наступний символ з вхідного потоку можна отримати і не видаляти його з потоку за допомогою функції peek(). Ось як виглядає її прототип:

int peek();

Функція peek() повертає наступний символ потоку, або значення EOF, якщо досягнуто кінець файлу. Зчитаний символ повертається в молодшому байті значення, що повертається функцією. Тому значення, що повертається функцією peek(), можна присвоїти змінній типу char.

Функція putback() повертає зчитаний символ у вхідний потік.

Останній символ, що зчитується з потоку, можна повернути у потік, використовуючи функцію putback(). Її прототип має такий вигляд:

istream &putback(char сh);

У цьому записі параметр сh містить символ, що зчитується з потоку останнім.

Функція flush() записує на диск вміст файлових буферів.

Під час виведення даних за допомогою функції flush()не відбувається негайного їх запису на фізичний пристрій, що є у даний момент пов'язаним з потоком. Інформація, що підлягає виведенню, спочатку накопичується у внутрішньому буфері доти, доки він цілком не заповниться. І тільки тоді його вміст переписується на диск. Проте існує можливість негайного перезапису на диск даних, що зберігається в буфері функції flush(), не чекаючи його повного заповнення. Цей засіб полягає у виклику функції flush(). Її прототип має такий вигляд:

ostream &flush();

До викликів функції flush() необхідно вдаватися у випадку, якщо програма призначена для роботи в несприятливих середовищах (для яких характерні часті відключення, наприклад, електрики).

19.5.8. Перевірка статусу введення-виведення

С++-система введення-виведення підтримує статусну інформацію про результати виконання кожної операції введення-виведення даних. Поточний статус потоку введення-виведення описується в об'єкті типу iostate, який є перерахунком (воно визначене у класі ios), що містить такі члени: