2.1. Потоковий ввод-вивід в стилі С
Особливістю С є відсутність в цій мові структурованих файлів. Всі файли розглядаються як не структурована послідовність байтів. При такому підході поняття файлу поширюється і на різні пристрої.
В С існують засоби введення-виведення. Всі операції введення-виведення реалізуються за допомогою функцій, які знаходяться в бібліотеці С. Бібліотека С підтримує три рівні введення-виведення:
• потоковий ввід-висновок;
• введення-виведення нижнього рівня;
• введення-виведення для консолі і портів (залежить від ОС).
Потік - це абстрактне поняття, що відноситься до будь переносу даних від джерела до приймача.
Читання даних з потоку називається витяганням, висновок у потік - приміщенням, або включенням.
Потік визначається як послідовність байтів і не зависит від конкретного пристрою, з яким проводиться обмін (оперативна пам'яти, файл на диску, клавіатура або принтер). Обмін з потоком для збільшення швидкості передачі даних виробляється, як правило, через спеціальну область оперативної пам'яті - буфер. Буфер накопичує байти, і фактична передача даних виконується після заповнення буфера. При введенні це дає можливість виправити помилки, якщо дані з буфера ще не відправлені в програму.
выходной поток |
буфер |
буфер |
входной поток |
программа |
Чтение из потока |
Помещение в поток |
Рисунок 25- Читання даних з потоку
При роботі з потоком можна:
• Відкривати і закривати потоки (пов'язувати покажчики на потік з конкретними файлами);
• вводити і виводити рядок, символ, відформатовані дані, порцію даних довільної довжини;
• аналізувати помилки вводу-виводу і досягнення кінця файлу;
• управляти буферизацією потоку і розміром буфера;
• отримувати і встановлювати покажчик поточної позиції у файлі.
Функції бібліотеки введення-виведення знаходяться в заголовному файлі <stdio.h>.
Перш ніж почати працювати з потоком, його треба ініціювати, тобто відкрити. При цьому потік пов'язується зі структурою зумовленого типу FILE, визначення якої знаходиться в бібліотечному файлі <stdio.h>. У структурі знаходиться покажчик на буфер, покажчик на поточну позицію файлу і т. п. При відкритті потоку, повертається покажчик на потік, тобто на об'єкт типу FILE.
# include <stdio.h>;
........
FILE * fp;
............
fp = fopen ("t.txt", "r");
де fopen (<ім'я файлу>, <режім_откритія>) - функція для ініціалізації файлу.
Існуючи режими для відкриття файлу представлені в таблиці
Таблиця 25 – Режими для відкриття файлу
Режим | Описание режима открытия файла |
r | Файл открывается для чтения, если файл не существует, то выдается ошибка при исполнении программы. |
w | Файл открывается для записи, если файл не существует, то он будет создан, если файл уже существует, то вся информация из него стирается. |
a | Файл открывается для добавления, если фай не существует, то он будет создан, если существует, то информация из него не стирается, можно выполнять запись в конец файла |
r+ | Файл открывается для чтения и записи, изменить размер файла нельзя, если файл не существует, то выдается ошибка при исполнении программы. |
w+ | Файл открывается для чтения и записи, если файл не существует, то он будет создан, если файл уже существует, то вся информация из него стирается. |
a+ | Файл открывается для чтения и записи, если фай не существует, то он будет создан, если существует, то информация из него не стирается, можно выполнять запись в конец файла |
Потік можна відкрити в текстовому (t) або довічним (b) режимі. За замовчуванням використовується текстовий режим. У явному вигляді режим вказується таким чином:
• "r + b" або "rb" - двійковий (бінарний) режим;
• "r + t" або "rt" - текстовий режим.
У файлі stdio.h визначена константа EOF, яка повідомляє про закінчення файлу (негативне ціле число).
При відкритті потоку можуть виникати такі помилки:
• файл, пов'язаний з потоком не знайдений (при читанні з файлу);
• диск заповнений (при записі);
• диск захищений від запису (при записі) і т. п.
У цих випадках покажчик на потік придбає значення NULL (0). Покажчик на потік, відмінний від аварійного не дорівнює 0.
Для виведення про помилку при відкритті потоку використовується стандартна бібліотечна функція з файлу <stdio.h>
void perror (const char * s);
if ((fp = fopen ("t.txt", "w") == NULL)
{
/ / Виводить рядок символів з повідомленням про помилку
perror ("\ nошібка при відкритті файлу");
exit (0);
}
Після роботи з файлом, його треба закрити
fclose (<указатель_на_поток>);
Коли програма починає виконуватися, автоматично відкриваються декілька потоків, з яких основними є:
• стандартний потік вводу (stdin);
• стандартний потік виводу (stdout);
• стандартний потік виводу про помилки (stderr).
За замовчуванням stdin ставиться у відповідність клавіатура, а потокам stdout і stderr - монітор. Для введення-виведення за допомогою стандартних потоків використовуються функції:
• getchar () / putchar () - введення-виведення окремого символу;
• gets () / puts () - ввід-вивід рядка;
• scanf () / printf () - форматований ввід / вивід.
Аналогічно роботі зі стандартними потоками виконується введення-виведення в потоки, пов'язані з файлами.
Для символьного введення-виведення використовуються функції:
• int fgetc (FILE * fp), де fp - покажчик на потік, з якого виконується зчитування. Функція повертає черговий символ у формі int з потоку fp. Якщо символ не може бути прочитаний, то повертається значення EOF.
• int fputc (int c, FILE * fp), де fp - покажчик на потік, в який виконується запис, c - змінна типу int, в якій міститься записуваний в потік символ. Функція повертає записаний в потік fp символ у формі int. Якщо символ не може бути записаний, то повертається значення EOF.
Для рядковому введення-виведення використовуються наступні функції:
• char * fgets (char * s, int n, FILE * f), де char * s - адреса, за якою розміщуються лічені байти, int n - кількість лічених байтів, FILE * f - покажчик на файл, з якого виробляється зчитування.
Прийом байтів закінчується після передачі n-1 байтів або при отриманні керуючого символу '\ n'. Керуючий символ теж передається в приймаючу рядок. Рядок у будь-якому випадку закінчується '\ 0'. При успішному завершенні роботи функція повертає покажчик на прочитану рядок, при неуспішному - 0.
• int puts (char * s, FILE * f), де char * s - адресу, з якого беруться записувані в файл байти, FILE * f - покажчик на файл, в який проводиться запис.
Символ кінця рядка ('\ 0') в файл не записується. Функція повертає EOF, якщо при записі у файл сталася помилка, при успішній запису повертає невід'ємне число.
Для блокового вводу-виводу використовуються функції:
• int fread (void * ptr, int size, int n, FILE * f), де void * ptr - вказівник на область пам'яті, в якій розміщуються лічені з файлу дані, int size - розмір одного зчитуваного елемента, int n - кількість прочитуваних елементів, FILE * f - покажчик на файл, з якого виробляється зчитування.
У разі успішного зчитування функція повертає кількість лічених елементів, інакше - EOF.
• int fwrite (void * ptr, int size, int n, FILE * f), де void * ptr - вказівник на область пам'яті, в якій розміщуються лічені з файлу дані, int size - розмір одного записуваного елемента, int n - кількість записуваних елементів, FILE * f - покажчик на файл, в який проводиться запис.
У разі успішної запису функція повертає кількість записаних елементів, інакше - EOF.
У деяких випадках інформацію зручно записувати у файл без перетворення, тобто в символьному вигляді придатному для безпосереднього відображення на екран. Для цього можна використовувати функції форматованого введення-виведення:
• int fprintf (FILE * f, const char * fmt,...), де FILE * f - покажчик на файл, в який проводиться запис, const char * fmt - форматна рядок,... - Список змінних, які записуються в файл.
Функція повертає число записаних символів.
• int fscanf (FILE * f, const char * fmt, par1, par2,...), де FILE * f - покажчик на файл, з якого проводиться читання, const char * fmt - форматна рядок, par1, par2,... - Список змінних, в які заноситься інформація з файлу.
Функція повертає число змінних, яким присвоєно значення.
Засоби прямого доступу дають можливість переміщати покажчик поточної позиції в потоці на потрібний байт. Для цього використовується функція
• int fseek (FILE * f, long off, int org), де FILE * f - покажчик на файл, long off - позиція зсуву, int org - початок відліку.
Зсув задається вираз або змінної і може бути негативним, тобто можливе переміщення як у прямому, так і в зворотному напрямках. Початок відліку задається однією із визначених у файлі <stdio.h> констант:
SEEK_SET == 0 - початок файлу;
SEEK_CUR == 1 - поточна позиція;
SEEK_END == 2 - кінець файлу.
Функція повертає 0, якщо переміщення в потоці виконано успішно, інакше повертає ненульове значення.
2.2. Обробка елементів файлу
Для того щоб видалити елемент з файлу потрібно використовувати допоміжний файл. У допоміжний файл переписуються всі елементи вихідного файлу за винятком тих, які потрібно видалити. Після цього вихідний файл видаляється з пам'яті, а допоміжному файлу привласнюється ім'я вихідного файлу.
void del (char * filename)
{/ / Видалення запису з номером х
FILE * f;/ / початковий файл
FILE * temp;/ / допоміжний файл
/ / Відкрити вихідний файл для читання
f = fopen (filename, "rb");
/ / Відкрити допоміжний файл для запису
temp = fopen ("temp", "wb")
student a;/ / буфер для читання даних з файлу
/ / Зчитуємо дані з вихідного файлу в буфер
for (long i = 0; fread (& a, sizeof (student), 1, f); i + +)
if (i! = x) / / якщо номер запису не дорівнює х
{
/ / Записуємо дані з буфера в тимчасовий файл
fwrite (& a, sizeof (student) 1, temp);
}
else
{
cout << a << "- is deleting...";
}
fclose (f);/ / закриваємо вихідний файл
fclose (temp); / / закриваємо тимчасовий файл
remove (filename);/ / видаляємо вихідний файл
rename ("temp", filename);/ / перейменовуємо тимчасовий файл
}
Для коригування елементів файлу використовується аналогічний алгоритм. Дані з вихідного файлу переписуються у допоміжний файл, але записи, які потрібно змінити записуються в відкоригованому вигляді.
Для додавання елементів в початок або в середину файлу також використовується допоміжний файл, в який в потрібне місце додаються нові дані.
Для додавання елементів кінець файлу достатньо відкрити його в режимі "a" або "a +" (для додавання) і записати нові дані в кінець файлу.
f = fopen (filename, "ab");/ / відкрити файл для додавання
cout << "\ nHow many records would you add to file?";
cin >> n;
for (int i = 0; i <n; i + +)
{
/ / Прочитати об'єкт
fwrite (& a, sizeof (student), 1, f);/ / записати в файл
}
fclose (f);/ / закрити файл
2.3. Потоковий ввод-вивід в стилі С + +
С + + надає можливість вводу / виводу як на низькому рівні - неформатований введення-виведення, так і на високому - форматований ввід-висновок. При неформатований вводі / виводі передача інформації здійснюється блоками байтів даних без будь-якого перетворення. При форматувати - байти групуються таким чином, щоб їх можна було сприймати як типізовані дані (цілі числа, рядки символів, числа з плаваючою комою і т. п.)
По напрямку обміну потоки можна розділити на
• вхідні (дані вводяться в пам'ять),
• вихідні (дані виводяться з пам'яті),
• двонаправлені (допускающие як витяг, так і включення).
По виду пристроїв, з якими працює потік, потоки можна розділити на стан-дротяні, файлові і рядкові:
• стандартні потоки призначені для передачі даних від клавіатури і на екран дисплея,
• файлові потоки - для обміну інформацією з файлами на зовнішних носіях даних (наприклад, на магнітному диску),
• рядкові потоки - для роботи з масивами символів в оперативній пам'яті.
Для роботи зі стандартними потоками бібліотека C + + містить бібліотеку <iostream.h>. При цьому в програмі автоматично стають доступними об'єкти:
• cin - об'єкт, відповідає стандартному потоку вводу,
• cout - об'єкт, відповідає стандартному потоку виводу.
Обидва ці потоку є буферізірованний.
Форматований ввід / вивід реалізується через дві операції:
• << - висновок в потік;
• >> - читання з потоку.
Використання файлів в програмі передбачає такі операції:
• створення потоку;
• відкриття потоку і зв'язування його з файлом;
• обмін (введення / виведення);
• знищення потоку;
• закриття файлу.
Для роботи зі файловими потоками бібліотека C + + містить бібліотеки:
• <ifstream.h> - для роботи з вхідними потоками,
• <ofstream.h> - для роботи з вихідними потоками
• <fstream.h> - для роботи з двонаправленими потоками.
В бібліотечних файлах потоки описані як класи, тобто являють собою користувальницькі типи даних (аналогічно структурам даних). В опис класу, крім даних, додаються описи функцій, що обробляють ці дані (відповідно компонентні дані і компонентні функції (методи)). Звертатися до компонентних функцій можна також як і до компонентних даними за допомогою. або ->.
Для створення файлового потоку використовуються спеціальні методи-конструктори, які створюють потік відповідного класу, відрозкривають файл з вказаним ім'ям і пов'язують файл з потоком:
• ifstream (const char * name, int mode = ios:: in);/ / вхідний потік
• ofstream (const char * name, int mode = ios:: out | ios:: trunc);/ / вихідний потік
• fstreamCconst char * name, int mode = ios:: in | ios:: out);/ / двонаправлений потік
Другим параметром є режим відкриття файлу. Замість значення за замовчуванням можна вказати одне з наступних значень, визначених в класі ios.
Таблиця 26 – Методи-конструктори
ios::in | открыть файл для чтения; |
ios::out | открыть файл для записи; |
ios::ate | установить указатель на конец файла, читать нельзя, можно только записывать данные в конец файла; |
ios::app | открыть файл для добавления; |
ios::trunc | если файл существует, то создать новый; |
ios::binary | открыть в двоичном режиме; |
ios::nocreate | если файл не существует, выдать ошибку, новый файл не открывать |
ios::noreplace | если файл существует, выдать ошибку, существующий файл не открывать; |
Відкрити файл у програмі можна з використанням або конструкторів, або методу open, має такі ж параметри, як і у відповідному конструкторі.
fstream f; / / створює файловий потік f
/ / Відкривається файл, який зв'язується з потоком
f.open (".. \ \ f.dat", ios:: in);
/ / Створює і відкриває для читання файловий потік f
fstream f (".. \ \ f.dat", ios:: in);
Після того як файловий потік відкритий, працювати з ним можна також як і зі стандартними потоками cin і cout. При читанні даних з вхідного файлу треба контролювати, чи був досягнутий кінець файлу після чергової операції виводу. Це можна робити за допомогою методу eof ().
Якщо в процесі роботи виникне помилкова ситуація, то потоковий об'єкт набуває значення рівне 0.
Коли програма залишає область видимості потокового об'єкта, то він знищується, при цьому перестає існувати зв'язок між потоковим об'єктом і фізичним файлом, а сам файл закривається. Якщо файл потрібно закрити раніше, то використовується метод close ().
/ / Створення файлу з елементів типу person
struct person
{
char name [20];
int age;
};
person * mas;/ / динамічний масив
fstream f ("f.dat", ios:: out);/ / двонаправлений файловий потік
int n;
cout << "N?";
cin >> n;
mas = new person [n];/ / створюємо динамічний масив
for (int i = 0; i <n; i + +)
{
cout << "?";
/ / Введення одного елемента типу person зі стандартного потоку cin
cin >> mas [i]. name;
cin >> mas [i]. age;
}
/ / Запис елементів масиву в файловий потік
for (i = 0; i <n; i + +)
{
f << mas [i]. name; f << "\ n";
f << mas [i]. age; f << "\ n";
}
f.close ();/ / закриття потоку
/ / Читання елементів з файлу
person p;
f.open ("f.dat", ios:: in);/ / відкриваємо потік для читання
do
{/ * Читаємо елементи типу person з файлового потоку f в змінну p * /
f >> p.name;f >> p.age;
/ / Якщо досягнуто кінець файлу, виходимо з циклу
if (f.eof ()) break;
/ / Вивід на екран
cout << p.name << "" << p.age << "\ n";
} While (! F.eof ());
f.close ();/ / закриття потоку