Інформатика
Файловий потік : Потокове введення і виведення
 

Потокове введення і виведення

     Класичний підхід, прийнятий в С, полягає в тому, що інформація про потік заноситься в структуру 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;
}

 
© 2008 ХНУРЭ, Інформатики, Сінельнікова Т.Ф., informatika@kture.Kharkov.ua
Розроблено за допомогою LERSUS