Потокове введення і виведення
Класичний підхід, прийнятий в С, полягає в тому, що інформація про потік заноситься в структуру FILE, яка визначена у файлі stdio.h. Файл відкривається за допомогою функції fopen, яка повертає покажчик на структуру типу FILE.
typedef struct
{
short level; //рівень буферу
unsigned flags; //статус файлу
char fd; //дескриптор файла
char hold; //попередній символ,якщо немає буферу
short bsize; //розмір буферу
unsigned char *buffer; //буфер передавання даних
unsigned char *curp; //поточний активний покажчик
short token; //перевірка коректності
} FILE;
Функція відкриття файла :
FILE *fopen(const char *filename, const char *mode);
де FІLE - це керуюча структура для потоку;
filename-ім'я файла;
mode - режим відкриття файла (таблиця 1).
Функція відкриває файл із заданим ім'ям і зв'язує з ним потік.
Таблиця 1 - Значення аргументу mode функції fopen()
"r" | Відкриття файлу без дозволу на модифікацію, файл відкривається лише для читання. |
"w" | Створення нового файлу тільки для запису, якщо файл із вказаним ім'ям вже існує, то він перезапишеться. |
"a" | Відкриття файлу тільки для додавання інформації в кінець файлу, якщо файл не існує, він створюється. |
"r+" | Відкриття існуючого файлу для читання та запису. |
"w+" | Створення нового файлу для читання та запису, якщо файл із вказаним ім'ям вже існує, то він перезаписується. |
"a+" | Відкриває файл у режимі читання та запису для додавання нової інформації у кінець файлу; якщо файл не існує, він створюється. |
"r" | Відкриття файлу без дозволу на модифікацію, файл відкривається лише для читання. |
"w" | Створення нового файлу тільки для запису, якщо файл із вказаним ім'ям вже існує, то він перезапишеться. |
"a" | Відкриття файлу тільки для додавання інформації в кінець файлу, якщо файл не існує, він створюється. |
"r+" | Відкриття існуючого файлу для читання та запису. |
"w+" | Створення нового файлу для читання та запису, якщо файл із вказаним ім'ям вже існує, то він перезаписується. |
"a+" | Відкриває файл у режимі читання та запису для додавання нової інформації у кінець файлу; якщо файл не існує, він створюється. |
До вказаних специфікаторів в кінці або перед символом "+" може додаватися символ "t" (текстовий файл), або "b" (бінарний, двійковий файл). При відсутності в рядку "b" або "t", режим визначається глобальною змінною _fmode, яка знаходиться в файлі fcntl.h.
Файлова змінна (потік) не призначена для прямого використання. Потік є програмною надбудовою над файлом, що надає програмістові додатковий сервіс. Крім спільного відкриття потоку й файлу (fopen), можна відкрити потік й асоціювати його із вже відкритим файлом (fdopen), відкрити файл й асоціювати його із уже відкритим потоком (freopen).
Закриває потік і вивантажує буфери функція:
іnt fclose(FІLE *stream ) ;.
Функція повертає 0 при успіху й EOF при помилці.
EOF - константа, яка знаходиться в stdіo.h.
іnt fcloseall(voіd);.
Функція закриває всі відкриті потоки, крім стандартних: stdіn, stdout, stdprn, stderr й stdaux.
Текстові файли
Розглянемо спочатку роботу з текстовими файлами. Відкриття текстового файлу test.txt може мати вигляд.
#include<stdio.h>
void main()
{
…
FILE *f;
if ((f=fopen("test.txt", "rt"))==NULL)
{
printf("Файл не вдалося відкрити.\n");
return 1;
}
…
fclose(f);
…
}
В даному прикладі змінна f зв'язується з файлом "test.txt", який відкривається як текстовий тільки для читання. З відкритого таким чином файлу можна читати інформацію. Після закінчення роботи з файлом, його необхідно закрити за допомогою функції fclose(). Якщо в програмі відкрито декілька файлів і вони можуть бути закриті одночасно, то для одночасного закриття файлів використовується функція fcloseall(). Якщо файл відкривався би за допомогою
fopen("test.txt", "rt+");,
то можна було б не тільки читати, але й записувати в нього інформацію.
З текстового файла можна читати інформацію по рядках, по символах або за форматом.
Читання символу з потоку виконується функцією:
іnt fgetc(FІLE *stream);
Функція повертає код символу. При помилці повертає EOF.
Запис символу в потік виконується функцією:
іnt fputc(іnt c, FІLE *stream); - повертає код символу c. При помилці повертає EOF.
Читання символу зі стандартного потоку stdіn виконується функцією
іnt fgetchar(voіd);
Запис символу в стандартний потік stdout виконується функцією
іnt fputchar(іnt c);
Приклад 1. Скопіювати файл у другий файл.
#include <stdio.h>
void main()
{
FILE *in = fopen ("noname00.cpp","rt");
FILE *out = fopen ("noname00.000","wt");
if (!in) return;
while (!feof(in))
fputc(fgetc(in), out);
fcloseall();
}
Функція feof() перевіряє, чи є прочитаний символ завершення файла. Якщо такий символ прочитаний, то feof() повертає ненульове значення і цикл завершується.
Читання рядка здійснюється за допомогою функції:
char *fgets(char *s,int n,FILE *stream);
де s - покажчик на буфер, в який читається рядок;
n - кількість символів.
Читання символу в рядок проходить або до появи символу кінця рядка "\n", або читається n-1 символ. В кінці прочитаного рядка записується нульовий символ. Читання припиняється, коли досягнуто кінця рядка або прочитано n-1 символів з файлу. Рядок у буфері замикається нульовим символом.
Запис рядка в потік виконується функцією:
char *fputs(char *s,FІLE *stream);.
Функція повертає покажчик на останній записаний символ або EOF при помилці. Нульовий символ рядка не копіюється.
Приклад 2. Скопіювати текстовий файл у другий файл по рядках.
#include <stdio.h>
void main()
{
const int n = 100;
char buf [n];
FILE *in = fopen ("noname00. cpp","r");
FILE *out = fopen ("noname00. 000","w");
if (!in) return;
while (!feof(in))
{
fgets(buf, n, in);
fputs(buf, out);
}
fcloseall();
}
Читання з текстового файлу форматованих даних може здійснюватися функцією:
int fscanf(FILE *stream, const char *format[, address, …]);
де stream - файловий потік;
format - визначає рядок форматування аргументів, які задаються своїми адресами(address).
При форматованому читанні можуть виникати помилки у зв'язку з досягненням завершення файлу або невірним форматом записаних у файлі даних. Перевірити, чи успішно пройшло читання даних можна за значенням, яке повертає функція fscanf(). При успішному читанні вона повертає кількість прочитаних полів. Тому читання даних можна організовувати наступним чином :
if (fscanf(f,"%d%d%d",&a,&b,&c)!=3)
{
printf("Помилка читання!\n");
};
Функція fscanf розглядає вхідний потік як послідовність полів введення. Поле введення закінчується:
- першим символом пробілу (але не включає його);
- першим символом, що не може бути перетворений по специфікації формату, зіставленої цьому полю;
- (n+1)-м символом, якщо специфікація включає ширину поля в n символів.
Функція переглядає послідовність полів введення, форматує їх і розміщує по адресах - аргументах fscanf. Число адрес, специфікацій формату й полів введення повинне бути погоджене.
Рядок формату складається з невідображуваних символів (' ',\t,\n), відображуваних символів (всі інші, крім '%') і специфікаторів формату. Якщо fscanf зустрічає невідображуваний символ у форматному рядку, вона буде зчитувати, але не зберігати всі невідображувані символи вхідного потоку аж до першого відображуваного символу. Якщо fscanf зустрічає відображуваний символ у форматному рядку, вона прочитає, але не збереже відповідний символ вхідного потоку.
Специфікація формату пропонує fscanf читання, перетворення й розміщення в пам'яті одного вхідного поля. Загальний вид специфікації формату:
%[*] [ширина] [модиф. розміру] [модиф. типу арг.] символ типу,
де * - скасовує присвоювання поля введення;
ширина - максимальне число символів,які зчитуються;
модифікатор розміру - N - near, F - far;
модифікатор типу аргументу - змінює тип адресного аргументу;
символ типу - символ специфікації типу - обов'язковий елемент формату.
Функція форматованного виведення :
int fprintf(FILE *stream, const char *format[,argument,…]);.
Зверніть увагу, що фукція fscanf повертає адресу на паратетр вводу, фукція fprintf отримує ім'я змінної для виведення в файл.
Приклад 3. Форматоване введення/виведення
#include<stdio.h>
void main()
{
FILE *fi;
int age;
fi=fopen("age.txt","r"); // відкриття файла для читання
fscanf(fi,"%d",&age); //читання з файла числового значення
fclose(fi); // закриття файла
fi=fopen("data.txt", "a"); // відкриття файла для додавання інформації в кінець
fprintf(fi, "Age==%d.\n",age); // запис рядка в файл
fclose(fi); // закриття файла
}
Нижче наведені інші функції форматного введення з вказівкою заголовного файлу й вхідного потоку.
cscanf - CONIO.H - консоль;
fscanf - STDIO.H - потік;
scanf - STDIO.H - stdin;
sscanf - STDIO.H - строка.
Те ж саме для форматного виведення
cprintf - CONIO.H - консоль;
fprintf - STDIO.H - потік;
printf - STDIO.H - stdout;
sprintf - STDIO.H - строка.
Двійкові файли
Двійковий файл представляє собою просто послідовність символів. Що саме і в якій послідовності зберігається в двійковому файлі - повинна знати програма. Двійкові файли мають переваги, порівняно з текстовими при зберіганні числових даних. Операції читання і запису з такими файлами виконуються набагато швидше, ніж з текстовими, так як відсутня необхідність форматування (переведення в текстове представлення та навпаки). Двійкові файли зазвичай мають менший розмір, ніж аналогічні текстові файли. В двійкових файлах можна переміщуватися в будь-яку позицію і читати або записувати дані в довільній послідовності, в той час, як в текстових файлах практично завжди виконується послідовна обробка інформації. Про те, як відкривати двійкові файли було згадано раніше.
Запис і читання в двійкових файлах виконується відповідно функціями:
size_t fwrite(const void *ptr, size_t size, size_t n, FILE*stream);
size_t fread(void *ptr, size_t size, size_t n, FILE *stream);
де ptr - покажчик на дані, які вводяться або виводяться;
size задає розмір в байтах даних, які читаються або записуються.
Приклад 4. Виведення в двійковий файл змінної типа структура.
#include<stdio.h>
#include<conio.h>
struct mystruct
{
int i;
char ch;
};
int main()
{
FILE *stream;
mystruct s;
if ((stream = fopen("test.txt", "wb")) == NULL)
{
fprintf(stderr, "Неможливо відкрити файл\n");
return 1;
}
s.i = 0;
s.ch = 'A';
fwrite(&s, sizeof(s), 1, stream);
fclose(stream);
return 0;
}
Розглянемо особливості записування і читання рядків.
char s[10];
strcpy(s, "Example");
…
fwrite(s,strlen(s)+1,sizeof(char),stream);
Виведення рядків відбувається посимвольно. В даному прикладі число символів, які записуються - strlen(s)+1 (одиниця додається на нульовий символ в кінці). Читається рядок аналогічно:
fread(s,strlen(s)+1,sizeof(char),stream);
При цьому читання проходить теж посимвольно.
Дуже часто доводиться працювати з рядками різних довжин. В таких випадках можна перед рядком записати у файл ціле число, яке рівне числу символів у рядку.
…
int i=strlen(s)+1;
fwrite(&i,1,sizeof(int),stream);
fwrite(s,i,1,stream);
…
fread(&i,1,sizeof(int),stream);
fread(s,i,1,stream)
В усіх наведених вище прикладах читання даних проходило послідовно. Але, працюючи з двійковими файлами, можна організувати читання даних в довільному порядку. Для цього використовується "покажчик файла" (курсор), який визначає поточну позицію у файлі. При читанні даних курсор автоматично зміщується на число прочитаних байтів. Отримати поточну позицію курсору файла можна за допомогою функції:
long ftell(FILE *stream);.
А встановлюється поточна позиція курсору у файлі за допомогою функції:
int fseek(FILE *stream, long offset, int whence);
де offset - зміщення на число байтів offset від точки відліку;
whence - параметр точки відліку.
Цей параметр whence може приймати значення 0, 1, 2.
Таблиця 2 - Можливі значення параметра whence функції fseek
Константа | Whence | Точка відліку |
SEEK_SET | 0 | Початок файлу |
SEEK_CUR | 1 | Поточна позиція |
SEEK_END | 2 | Кінець файлу |
Якщо задане значення whence=1, то offset може приймати як додатне, так і від'ємне значення, тобто зсув вперед або назад.
Функція rewind() переміщує курсор на початок файлу:
void rewind(FILE *stream);.
Те ж саме можна зробити за допомогою функції fseek() :
fseek(stream, 0L, SEEK_SET);.
Приклад 5. Программа, яка демонструє роботу наведенних функцій.
#include <stdio.h>
long filesize(FILE *stream);
int main()
{
FILE *stream;
stream = fopen("test.txt", "w+");
fprintf(stream, "This is a test");
printf("Розмір файла test.txt рівний %ld байт\n",
filesize(stream));
fclose(stream);
return 0;
}
long filesize(FILE *stream)
{
long curpos, length;
curpos = ftell(stream);
fseek(stream, 0L, SEEK_END);
length = ftell(stream);
fseek(stream, curpos, SEEK_SET);
return length;
}