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), що містить такі члени: