Тема: Работа с файлами в Си. Доступ к файлам.
1.Работа с текстовыми файлами
2.Работа с двоичными файлами
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' в конце каждой строки (кроме последней), поэтому строки будут выведены в выходной файл так же, как они были записаны в исходном файле.
|