Открытие и закрытие файлов
Ранее мы использовали стандартные потоки — клавиатуру и монитор для ввода-вывода данных. Теперь познакомимся с тем, как в языке C реализуется работа с файлами, ведь перед чтением или записью данных необходимо сначала открыть файл, чтобы получить к нему доступ.
В языке C указатель на файл обозначается типом FILE, и его объявление выглядит следующим образом:FILE *myfile;
Используя функцию fopen()
, можно открыть файл, предоставив адрес файла в качестве первого аргумента. Это можно сделать в режиме чтения ("r"), записи ("w")
или добавления ("a"). Функция возвращает указатель на открытый файл, и поэтому открытие файла обычно оформляется так:myfile = fopen ("hello.txt", "r");
Все операции чтения и записи в файл осуществляются через файловый указатель, в нашем случае - это myfile.
Если функция fopen()
не сможет открыть файл из-за отсутствия файла по указанному адресу или запрета доступа, она вернет NULL. В
большинстве программ подобные ошибки обрабатываются внутри if
, но мы сейчас опустим это.
Заголовочный файл stdio.h содержит объявление функции fopen()
и описывает тип-структуру FILE, поэтому требуется его подключение.
После завершения работы с файлом рекомендуется его закрывать. Это позволяет освободить буфер данных и по другим причинам. Особенно важно это делать, если программа продолжает работу после взаимодействия с файлом. Закрытие
файла и отключение указателя от него осуществляется с помощью функции fclose()
. Ей передается указатель на файл:fclose(myfile);
В одной программе может быть открыто несколько файлов одновременно. Каждый из них должен быть связан с собственным файловым указателем. Однако, если один файл уже закрыт, указатель может быть использован для открытия нового файла.
Чтение из текстового файла и запись в него
fscanf()
Функция fscanf()
работает аналогично scanf()
, но предназначена для форматированного ввода из файла, а не из стандартного ввода.
Параметры функции fscanf()
включают файловый указатель, строку формата и адреса памяти для сохранения данных:fscanf (myfile, "%s%d", str, &a);
Она возвращает количество успешно считанных данных или EOF. Пробелы и символы новой строки интерпретируются как разделители.
Допустим, файл содержит следующие данные об объектах:
apples 10 23.4
bananas 5 25.0
bread 1 10.3
Пример программы для чтения данных:
#include <stdio.h>
main () {
FILE *file;
struct food {
char name[20];
unsigned qty;
float price;
};
struct food shop[10];
char i=0;
file = fopen("fscanf.txt", "r");
while (fscanf (file, "%s%u%f", shop[i].name, &(shop[i].qty), &(shop[i].price)) != EOF) {
printf("%s %u %.2f\n", shop[i].name, shop[i].qty, shop[i].price);
i++;
}
}
В этом фрагменте программы объявляется структура и массив структур. Каждая строка файла соответствует элементу массива, который представляет собой структуру с
строковым и двумя числовыми полями. Цикл производит одну итерацию для считывания строки, и при встрече EOF fscanf()
прекращает цикл.
fgets()
Функция fgets()
похожа на gets()
и используется для построчного ввода из файла. Каждое применение fgets()
стремится считать одну строку. Можно
указать чтение лишь части строки, начиная с её начала. Параметры функции следующие:fgets (
массив_символов, количество_символов, файловый_указатель)
Пример:fgets (str, 50, myfile)
Этот пример сканирует строку, связанную с указателем myfile. Если строка меньше 50 символов, она будет прочтена полностью, включая '\n', который сохранится в массиве, но последним (50-ым)
символом будет '\0', добавленный fgets()
. Если строка длиннее, читается 49 символов, и '\0' помещается в конец строки, а '\n' будет отсутствовать.
#include <stdio.h>
#define N 80
main () {
FILE *file;
char arr[N];
file = fopen("fscanf.txt", "r");
while (fgets (arr, N, file) != NULL)
printf("%s", arr);
printf("\n");
fclose(file);
}
В этом примере программа считывает данные строками в массив arr. Когда новая строка считывается, предыдущая перезаписывается. Как только fgets()
не может считать строку,
она возвращает NULL.
getc() или fgetc()
Функции getc()
и fgetc()
обе позволяют извлечь один символ из файла.
while ((arr[i] = fgetc (file)) != EOF) {
if (arr[i] == '\n') {
arr[i] = '\0';
printf("%s\n",arr);
i = 0;
}
else i++;
}
arr[i] = '\0';
printf("%s\n",arr);
Представленный код считывает символы из файла и выводит их на экран.
Запись в текстовый файл
Процесс записи в файл может быть разным, как и ввод.
- Форматированный вывод осуществляется функцией
fprintf (
файловый_указатель, строка_формата, переменные)
. - Построчная запись происходит с помощью функции
fputs (
строка, файловый_указатель)
. - Запись по символам поддерживается функцией
fputc()
или с помощьюputc(
символ, файловый_указатель)
.
Примеры использования функций для записи данных в файл:
Запись каждой структуры в отдельной строке файла:
file = fopen("fprintf.txt", "w");
while (scanf ("%s%u%f", shop[i].name, &(shop[i].qty), &(shop[i].price)) != EOF) {
fprintf(file, "%s %u %.2f\n", shop[i].name, shop[i].qty, shop[i].price);
i++;
}
Построчная запись в файл (функция fputs()
сама не добавляет '\n' в конце):
while (gets (arr) != NULL) {
fputs(arr, file);
fputs("\n", file);
}
Пример посимвольной записи:
while ((i = getchar()) != EOF)
putc(i, file);
Чтение из двоичного файла и запись в него
В работе с файлами можно перешагнуть за текст и обращаться к файлу как к последовательности байтов. Такое обращение необходимо для работы с бинарными данными. Однако, это применимо и для работы с текстовыми файлами. Основное преимущество бинарного доступа заключается в ускоренной записи-чтении, так как за раз можно обработать большой блок данных.
Для открытия файла в двоичном режиме используется строка "rb" или "wb" в качестве второго параметра функции fopen()
.
Работа с бинарными файлами является сложной темой и требует отдельного занятия. Здесь же будут указаны особенности функций для чтения-записи в файл как в поток байтов.
Функции fread()
и fwrite()
принимают параметры:
- адрес памяти, куда записываются или откуда считываются данные,
- размер одного элемента определенного типа,
- число элементов данных для операции,
- файловый указатель.
Эти функции возвращают количество удачно обработанных элементов. К примеру, вы можете запросить чтение 50 элементов, но получить только 10, без ошибки.
Пример использования fread()
и fwrite()
:
#include <stdio.h>
#include <string.h>
main () {
FILE *file;
char shelf1[50], shelf2[100];
int n, m;
file = fopen("shelf1.txt", "rb");
n=fread(shelf1, sizeof(char), 50, file);
fclose(file);
file = fopen("shelf2.txt", "rb");
m=fread(shelf2, sizeof(char), 50, file);
fclose(file);
shelf1[n] = '\0';
shelf2[m] = '\n';
shelf2[m+1] = '\0';
file = fopen("shop.txt", "wb");
fwrite(strcat(shelf2,shelf1), sizeof(char), n+m, file);
fclose(file);
}
Здесь происходит попытка считать 50 символов из первого файла. В переменной n сохраняется количество реально считанных символов, которое может быть 50 или меньше. Данные сохраняются в строковой переменной. Аналогичное действие выполняется для второго файла. Далее первая строка присоединяется ко второй и записывается в третий файл.
Решение задач
- Создайте программу, которая будет запрашивать у пользователя имя (адрес) текстового файла, открывать его и подсчитывать количество символов и строк.
- Разработайте программу, записывающую данные из одного файла в другой с возможностью изменения данных перед записью. Каждая строка из исходного файла должна быть представлена в виде структуры.