<< Предыдущая Оглавление Следующая >>


5.2. Работа с бинарными файлами

Следует отметить, что во всех рассмотренных выше примерах функция fopen() в режимах “r” и “w” открывает текстовый файл на чтение и запись соответственно. Это означает, что некоторые символы форматирования текста, например возврат каретки ‘\r’ не могут быть считаны как отдельные символы, их как бы не существует в файле, но при этом они там есть. Это особенность текстового режима файла. Для более «тонкой» работы с содержимом файлов существует бинарный режим, который представляет содержимое файла как последовательность байтов где все возможные управляющие коды являются просто числами. Именно в этом режиме возможно удаление или добавление управляющих символов недоступных в текстовом режиме. Для того чтобы открыть файл в бинарном режиме используется также функция fopen() с последним параметром равным “rb” и “wb” соответственно для чтения и записи. Продемонстрируем особенности обработки бинарного файла на примере подсчета числа управляющих символов возврата каретки ‘\r’ в файле, открытый в текстовом режиме и бинарном.

Листинг 5.6. Программа подсчета числа символов ‘\r’ в файле.

#include
int main(void)
{
FILE* fp = fopen("my_file.txt","w");
if(fp != NULL)
{
fprintf(fp,"It is\nan example using\nan binary file.");
}
fclose(fp);
char ch;
int cnt = 0;
fp = fopen("my_file.txt","r");
if(fp != NULL)
{
while((ch = getc(fp)) != EOF)
if(ch == '\r') cnt++;
}
fclose(fp);
printf("Text file: cnt = %d\n",cnt);
cnt=0;
fp = fopen("my_file.txt","rb");
if(fp != NULL)
{
while((ch = getc(fp)) != EOF)
if(ch == '\r') cnt++;
}
fclose(fp);
printf("Binary file: cnt = %d\n",cnt);
return 0;
}

Результат работы будет следующий:

Text file: cnt = 0
Binary file: cnt = 2

Анализ полученных данных показывает, что при открытии файла в текстовом режиме, символы возврата каретки ‘\r’ не считываются функцией getc(), а в бинарном режиме доступны все символы.

Еще одной особенностью текстового формата файла является запись чисел в виде текста. Действительно, когда в предыдущих примерах выполнялась запись числа в файл с помощью функции fprintf(), например, года издательства книги, то число заменялось строкой. А когда она считывалась функцией fscanf(), то преобразовывалась обратно в число. Если мы хотим компактно представлять данные в файле, то числа следует хранить как числа, а не как строки. При этом целесообразно использовать бинарный режим доступа к файлу, т.к. будет гарантия, что любое записанное число не будет восприниматься как управляющий символ и будет корректно считан из файла.

Для работы с бинарными файлами предусмотрены функции fread() и fwrite() со следующим синтаксисом:

size_t fread( void *buffer, size_t size, size_t count, FILE *stream );

где *buffer – указатель на буфер памяти, в который будут считываться данные из файла; size – размер элемента в байтах; count - число считываний элементов; *stream – указатель на файл.

size_t fwrite( void *buffer, size_t size, size_t count, FILE *stream );

где *buffer – указатель на буфер памяти, из которого будут считываться данные в файл; size – размер элемента в байтах; count - число записей; *stream – указатель на файл.

Приведем пример использования функций fwrite() и fread().

Листинг 5.7. Использование функций fwrite() и fread().

#include
void main( void )
{
FILE *stream;
char list[30];
int i, numread, numwritten;
if( (stream = fopen( "fread.out", "wb" )) != NULL )
{
for ( i = 0; i < 25; i++ )
list[i] = (char)('z' - i);
numwritten = fwrite( list, sizeof( char ), 25, stream );
printf( "Wrote %d items\n", numwritten );
fclose( stream );
}
else printf( "Problem opening the file\n" );
if( (stream = fopen( "fread.out", "rb" )) != NULL )
{
numread = fread( list, sizeof( char ), 25, stream );
printf( "Number of items read = %d\n", numread );
printf( "Contents of buffer = %.25s\n", list );
fclose( stream );
}
else printf( "File could not be opened\n" );
}

В данном примере массив list выступает в качестве буфера для вывода и ввода информации из бинарного файла. Сначала элементы буфера инициализируются буквами латинского алфавита от z до b, а затем записываются в файл с помощью функции fwrite( list, sizeof( char ), 25, stream ). Здесь оператор sizeof( char ) указывает размер элемента (буквы), а число 25 соответствует числу записываемых букв. Аналогичным образом осуществляется считывание информации из файла fread( list, sizeof( char ), 25, stream ), где в массив list помещаются 25 символов, хранящихся в файле.

Функции fwrite() и fread() удобно использовать при сохранении данных структуры в файл. Запишем пример хранения информации по двум книгам в бинарном файле.

Листинг 5.8. Пример сохранения структур в бинарном файле.

#include
#define N 2
struct tag_book
{
char name[100];
char author[100];
int year;
} books[N];

int main(void)
{
for(int i=0;i < N;i++)
{
scanf("%s",books[i].name);
scanf("%s",books[i].author);
scanf("%d",&books[i].year);
}
FILE* fp = fopen("my_file.txt","wb");
fwrite(books, sizeof(books),1,fp);
fclose(fp);

fp = fopen("my_file.txt","rb");
fread(books,sizeof(books),1,fp);
fclose(fp);
printf("------------------------------------------------\n");
for(i=0;i < N;i++)
{
puts(books[i].name);
puts(books[i].author);
printf("%d\n",books[i].year);
}
return 0;
}

В данном примере с помощью функции fwrite() целиком сохраняется массив books, состоящий из двух элементов, а оператор sizeof(books) определяет размер массива books. Аналогичным образом реализуется и функция fread(), которая считывает из файла сразу весь массив. По существу функции fwrite() и fread(), в данном примере, осуществляют копирование заданной области памяти в файл, а затем обратно. Это их свойство удобно использовать при хранении «сложных» форм данных, когда простая поэлементная запись данных в файл становится трудоемкой или невозможной.

Следует отметить, что функция fopen() при открытии файла на запись уничтожает все данные из этого файла, если они были. Вместе с тем существует необходимость добавлять данные в файл, не уничтожая ранее записанную информацию. Это достигается путем открытия файла на добавление информации. В этом случае функции fopen() третьим аргументом передается строка “a” или “ab”, что означает открыть файл на добавление информации в его конец. Продемонстрируем работу данного режима на следующем примере.

Листинг 5.9. Добавление информации в файл.

#include
#define N 2
struct tag_book
{
char name[100];
char author[100];
int year;
} books[N];
int main(void)
{
for(int i=0;i < N;i++)
{
scanf("%s",books[i].name);
scanf("%s",books[i].author);
scanf("%d",&books[i].year);
}
FILE* fp = fopen("my_file.txt","wb");
fwrite(&books[0], sizeof(tag_book),1,fp);
fclose(fp);
fp = fopen("my_file.txt","ab");
fwrite(&books[1], sizeof(tag_book),1,fp);
fclose(fp);
fp = fopen("my_file.txt","rb");
fread(books,sizeof(books),1,fp);
fclose(fp);
printf("------------------------------------------------\n");
for(i=0;i < N;i++)
{
puts(books[i].name);
puts(books[i].author);
printf("%d\n",books[i].year);
}
return 0;
}

В данном примере сначала создается файл my_file.txt, в который записывается информация по первой книге. Затем открывается этот же файл в режиме добавления и записывается информация по второй книге. В результате файл my_file.txt содержит информацию по обеим книгам, что подтверждается считыванием данных из этого файла и выводом информации на экран.

Когда стандартные функции возвращают EOF, это обычно означает, что они достигли конца файла. Однако это также может означать ошибку ввода информации из файла. Для того чтобы различить эти две ситуации в языке С++ существую функции feof() и ferror(). Функция feof() возвращает значение отличное от нуля, если достигнут конец файла и нуль в противном случае. Функция ferror() возвращает ненулевое значение, если произошла ошибка чтения или записи, и нуль – в противном случае. Пример использования данных функций представлен в листинге 5.10.

Листинг 5.10. Использование функции ferror().

#include
void main( void )
{
int count, total = 0;
char buffer[100];
FILE *fp;

if( (fp = fopen( "my_file.txt", "r" )) == NULL )
return;
while( !feof( fp ) )
{
count = fread( buffer, sizeof( char ), 100, fp );
if( ferror( fp ) ) {
perror( "Read error" );
break;
}
total += count;
}
printf( "Number of bytes read = %d\n", total );
fclose( fp );
}

В языке С++ имеются также функции remove() и rename() для удаления и переименования файлов. Их синтаксис следующий:

int remove( const char *path );

где *path – путь с именем удаляемого файла. Данная функция определена в библиотеках stdio.h и io.h, возвращает нуль при успешном удалении и -1 в противном случае.

int rename( const char *oldname, const char *newname );

где *oldname – имя файла для переименования; *newname – новое имя файла. Данная функция определена в библиотеках stdio.h и io.h, возвращает нуль при успешном удалении и не нуль в противном случае.


<< Предыдущая Оглавление Следующая >>