Двоичные файлы

Дисциплина «Основы алгоритмизации и программирования» 3Т

Файлы бывают текстовые (в которых можно записывать только буквы, цифры, скобки и т.п.) и двоичные (в которых могут храниться любые символы из таблицы).

Как работать с файлами из программы

Понятие «открыть файл» означает «начать с ним работу», сделать его активным и заблокировать обращение других программ к этому файлу. При закрытии файла он освобождается (теперь с ним могут работать другие программы) и все ваши изменения вносятся на диск.

Для работы с файлом используется специальная переменная, которая называется указателем на файл. Это адрес блока данных в памяти, в котором хранится вся информация об открытом файле. Объявляется указатель на файл так:

FILE *fp;

Чтобы открыть файл, надо вызвать функцию fopen, которая попытается открыть файл и записать его адрес в переменную fp. После этого все обращения к файлу выполняются не по имени файла, а через указатель fp.

fp = fopen ( «qq.dat», «r» );

Здесь файл qq.dat из текущего каталога открывается для чтения (режим «r» во втором параметре функции fopen). Если надо, можно указать полный (или относительный) путь к файлу, например так:

fp = fopen ( «c:\\data\\qq.dat», «r» );

Знак «наклонные черта» (слэш) в символьных строках всегда удваивается, потому что одиночный слэш – это специальный символ, например в сочетании \n.

Режимы работы с файлами:

«r» Запись в новый файл. Если на диске уже есть файл с таким именем, он будет

предварительно удален.

«a» Добавление в конец файла. Если на диске уже есть файл с таким именем, новые данные дописываются в конец файла. Если такого файла нет, то он будет создан.

«r+» Открыть существующий файл для изменения с возможностью записи и чтения

«w+» Создание нового файла для записи и чтения (если файл с таким именем уже есть, он заменяется новым).

Иногда программа не может открыть файл. Если файл открывается на чтение, это возможно в следующих случаях:

• неверно задано имя файла или файла нет на диске;

• файл используется другой программой и заблокирован.

Если файл открывается на запись, операция может закончиться неудачно, если

• на диске нет места;

• файл защищен от записи;

• неверно задано имя файла (например, оно содержит две точки, знак вопроса и т.п.).

Если файл не удалось открыть, функция fopen возвращает специальное нулевое значение (нулевой указатель), который обозначается NULL. Поэтому надо всегда проверять правильность открытия файла, особенно в режиме чтения. Если файл не был открыт, надо вывести сообщение об ошибке и выйти из программы.

if ( fp == NULL )

{

printf(«Нет файла с данными»);

return 1; // выход по ошибке, код ошибки 1

}

Если файл открыт, можно читать из него данные. Для того используем функцию fscanf. Она полностью аналогична scanf, но служит для ввода из файла, а не с клавиатуры. Кроме того, ее первый параметр – указатель на файл, из которого читаются данные.

n = fscanf ( fp, «%d», &A[i] );

Функция fscanf возвращает результат – количество чисел, которые ей удалось прочитать.

Если мы запрашивали одно число, то значение переменой n может быть равно единице (если все нормально) или нулю (если данные закончились или ошибочные, например, вместо чисел введено слово). Для успешного чтения данные в файле должны отделяться пробелом или символом перехода на новую строчку (он вставляется в файл при нажатии на клавишу Enter).

Если файл открыт на запись, можно записать в него данные с помощью функции fprintf, которая полностью аналогична printf.

Когда работа с файлом закончена, надо закрыть его, вызвав функцию fclose:

fclose ( fp );

После этого указатель fp свободен и его можно использовать для работы с другим файлом.

Пример1. Ввести массив из 10 целых чисел из файла input.dat, умножить каждый элемент на 2 и вывести в столбик в файл output.dat.

В программе обрабатываются две ошибки:

• файла нет (его не удалось открыть);

• в файле мало данных или данные неверные (например, слова вместо целых чисел).

#include <stdio.h>

const int N = 10;

main()

{int i, A[N];

FILE *fp; // указатель на файл

fp = fopen( «input.dat», «r» );                                      // открыть файл на чтение

if ( fp == NULL )

{                                                                         // обработка ошибки

printf(«Нет файла данных»);

return 1;                                                              // выход по ошибке, код ошибки 1

}

for ( i = 0; i < N; i ++ )

if ( 0 == fscanf(fp,»%d»,&A[i]) )

{         printf(«Не хватает данных в файле»);               // чтение и обработка ошибки

break; }

fclose ( fp );                                                        // закрыть файл

for ( i = 0; i < N; i ++ )

A[i] = A[i] * 2;

fp = fopen( «output.dat», «w» );                                     // открыть файл на запись

for ( i = 0; i < N; i ++ )                                         // вывести массив в файл

fprintf ( fp, «%d\n», A[i] );                                   // в столбик

fclose ( fp ); }

В отличие от предыдущих, эта программа выдает результаты не на экран, а в файл

output.dat в текущем каталоге.

Пример 2. В файле input.dat записаны в два столбика пары чисел (x,y). Записать в файл output.dat в столбик суммы x+y для каждой пары.

Сложность этой задачи состоит в том, что мы не можем прочитать все данные сразу в память, обработать их и записать в выходной файл. Не можем потому, что не знаем, сколько пар чисел в массиве. Конечно, если известно, что в файле, скажем, не более 200 чисел, можно выделить массив «с запасом», прочитать столько данных, сколько нужно, и работать только с ними. Однако в файле могут быть миллионы чисел и такие массивы не поместятся в памяти.

Однако, если подумать, становится понятно, что для вычисления суммы каждой пары нужны только два числа, а остальные мы можем не хранить в памяти. Когда вычислили их сумму, ее также не надо хранить в памяти, а можно сразу записать в выходной файл. Поэтому будем использовать такой алгоритм:

1) открыть два файла, один на чтение (с исходными данными), второй – на запись;

2) попытаться прочитать два числа в переменные x и y; если это не получилось (нет больше данных или неверные данные), закончить работу;

3) сложить x и y и записать результат в выходной файл;

4) перейти к шагу 2.

Для того, чтобы определить, удачно ли закончилось чтение, мы будем использовать тот факт, что функция fscanf (как и scanf) возвращает количество удачно считанных чисел. За один раз будем читать сразу два числа, x и y. Если все закончилось удачно, функция fscanf возвращает значение 2 (обе переменных прочитаны). Если результат этой функции меньше двух, данные закончились или неверные.

Заметим, что надо работать одновременно с двумя открытыми файлами, поэтому в памяти надо использовать два указателя на файлы, они обозначены именами fin и fout. Для сокращения записи ошибки при открытии файлов не обрабатываются.

#include <stdio.h>

main()

{

int n, x, y, sum;

FILE *fin, *fout;                                                            // указатели на файлы

fin = fopen( «input.dat», «r» );                                       // открыть файл на чтение

fout = fopen( «output.dat», «w» );                                  // открыть файл на запись

while ( 1 ) {

n = fscanf ( fin, «%d%d», &x, &y );

if ( n < 2 ) break;                                // данные ошибочны или нет больше данных

sum = x + y;

fprintf ( fout, «%d\n», sum );

}

fclose ( fout );                                                                 // закрыть файлы

fclose ( fin );

}

В программе используется бесконечный цикл while. Программа выходит из него тогда, когда данные в файле закончились.

Работа с двоичными файлами

Двоичные файлы отличаются от текстовых тем, что в них записана информация во внутреннем машинном представлении. Двоичный файл нельзя просмотреть на экране (вернее, можно просмотреть, но очень сложно понять). Но есть и преимущества – из двоичных файлов можно читать сразу весь массив в виде единого блока. Также можно записать весь массив или его любой непрерывный кусок за одну команду.

При открытии двоичного файла вместо режимов «r», «w» и «a» используют соответственно «rb», «wb» и «ab». Дополнительная буква «b» указывает на то, что файл двоичный (от английского слова binary – двоичный). Приведем решение одной задачи, которую мы уже разбирали ранее.

Пример 1. Ввести массив из 10 целых чисел из двоичного файла input.dat, умножить каждый элемент на 2 и вывести в двоичный файл output.dat.

#include <stdio.h>

const int N = 10;

main()

{

int i, n, A[N];

FILE *fp;                                                                                // указатель на файл

fp = fopen( «input.dat», «rb» );                             // открыть двоичный файл на чтение

n = fread ( A, sizeof(int), N, fp );                                             // читаем весь массив

if ( n < N ) {                                                                            // обработка ошибки

printf(«Не хватает данных в файле»);

break;

}

fclose ( fp );                                                                             // закрыть файл

for ( i = 0; i < N; i ++ )

A[i] = A[i] * 2;

fp = fopen( «output.dat», «wb» );                // открыть двоичный файл на запись

fwrite ( A, sizeof(int), N, fp );                     // записать весь массив

fclose ( fp );                                                 // закрыть файл

}

Для чтения из двоичного файла используется функция fread, которая принимает 4 параметра:

• адрес области в памяти, куда записать прочитанные данные (в данном случае это адрес первого элемента массива A, который обозначается как &A[0] или просто A);

• размер одного элемента данных (лучше сделать так, чтобы машина сама определила его, например, в нашем случае – sizeof(int) – размер целого числа. Хотя в C++ целое число занимает 4 байта, в в других системах программирования это может быть не так; наша программа будет работать и в этом случае, то есть станет переносимой на другую платформу;

• количество элементов данных в массиве (N);

• указатель на открытый файл, откуда читать данные (fp).

Функция fread возвращает количество успешно прочитанных элементов массива – ее возвращаемое значение можно использовать для обработки ошибок. Если функция fread вернула значение, меньшее, чем N, в файле не хватает данных.

Для записи массива в двоичный файл используется функция fwrite с такими же параметрами; она возвращает количество успешно записанных элементов.

Преимущество этого способа состоит в том, что массив читается и записывается сразу единым блоком. Это значительно увеличивает скорость записи на диск (в сравнении с выводом в текстовый файл отдельно каждого элемента).

Чтение строк из файлов

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

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

Пример 1. Чтения слова из открытого файла с указателем fp.

#include <stdio.h>

main()

{

char s[80];

FILE *fp;

fp = fopen ( «input.dat», «r» );

fscanf ( fp, «%s», s );

printf ( «Первое слово файла — %s», s );

fclose ( fp );

}

Если надо читать всю строку с пробелами, используют функцию fgets. Она принимает три параметра:

• имя символьной строки, в которую записать данные;

• максимальную длину этой строки; функция не допускает выхода за границы строки; если строка в файле длиннее, чем можно записать в память, читается только начальная часть, а остальное – при следующих вызовах fgets;

• указатель на файл.

Если функция fgets не может прочитать строку из файла (например, если нет больше строк), то она возвращает в качестве результата специальное значение NULL. Это свойство можно использовать для обработки ошибок и прекращения ввода данных.

#include <stdio.h>

main()

{

char s[80];

FILE *fp;

fp = fopen ( «input.dat», «r» );

if ( NULL == fgets ( s, 80, fp ) )

printf ( «Не удалось прочитать строку» );

else

printf ( «Первая строка файла — %s», s );

fclose ( fp );}

Функция fgets читает строку из файла, пока не случится одно из двух событий:

• встретится символ перехода на новую строку ‘\n’;

• прочитано столько символов, что они заняли всю строку (с учетом последнего нуля), например, в нашем случае она остановится, если прочтет 79 символов.

В конце строки будет поставлен символ ‘\0’. Кроме того, если был найден символ перехода на новую строку ‘\n’, он сохраняется и в строке s.

Пример 2. В каждой строке файла input.dat заменить все буквы ‘A’ на ‘Б’ и вывести измененный текст в файл output.dat.

Обратите внимание, что в этой задаче файл может быть любой длины. Но мы можем обрабатывать строки последовательно одну за другой, так как для обработки одной строки не нужны предыдущие и следующие.

Для чтения из файла используем цикл while. Он заканчивает работу, если функция fgets вернет значение NULL, то есть все строки обработаны.

#include <stdio.h>

main()

{

char s[80];

int i;

FILE *fin, *fout;

fin = fopen ( «input.dat», «r» );

fout = fopen ( «output.dat», «w» );

while ( NULL != fgets ( s, 80, fin ) )                     // читаем строку

{ i = 0; // начинаем с s[0]

while ( s[i] != ‘\0’ ) {                                             // пока не конец строки

if ( s[i] == ‘А’ ) s[i] = ‘Б’;                                                // меняем символ

i ++;                                         // переходим к следующему символу

}

fprintf ( fout, «%s», s );                               // выводим строку в файл

}

fclose ( fin );

fclose ( fout );}

Обратите внимание, что мы не поставили символ перехода на новую строку при вызове функции fprintf. Это связано с тем, что при чтении функция fgets сохраняет символ ‘\n’ в конце каждой строки (кроме последней), поэтому строки будут выведены в выходной файл так же, как они были записаны в исходном файле.

Оставить комментарий

avatar
  Подписаться  
Уведомление о