Перейти к основному содержимому

Работа с файлами

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

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

  • fopen(): открывает файл и возвращает указатель на него.
  • fclose(): закрывает файл, освобождая ресурсы и гарантирует, что все данные будут записаны на диск.

Пример открытия и закрытия файла:

 #include <stdio.h>

int main(void){
FILE *f;
f = fopen("in.txt","w"); // открытие файла in.txt на запись
fclose(f); //Закрытие файла.

return 0;
}

Функция fopen() принимает два аргумента: имя файла и режим открытия.

Текстовые файлы

в функции fopen() режимы открытия для текстового файла могут быть:

  • r – существующий текстовый файл открывается для чтения.
  • w – создается новый текстовый файл и открывается на запись. Если файл с таким именем существовал ранее, то его содержимое удаляется.
  • a – текстовый файл открывается для записи в конец файла (его "старое" содержимое сохраняется) или создается.
  • r+ – существующий текстовый файл открывается для чтения и записи, текущая позиция (место в файле, по которому происходит чтение или запись) устанавливается в начало файла.
  • w+ – текстовый файл открывается или создается для чтения и записи, текущая позиция устанавливается в начало файла. Если файл с таким именем существовал ранее, то его содержимое. удаляется
  • а+ – текстовый файл открывается для чтения и записи, текущая позиция устанавливается в конец файла. Если файл не существовал, то он создаётся.

Чтение данных из текстовых файлов

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

fgetc()

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

#include <stdio.h>

int main() {
FILE *file = fopen("example.txt", "r");
if (file == NULL) {
perror("Ошибка при открытии файла");
return 1;
}

int ch;
while ((ch = fgetc(file)) != EOF) {
putchar(ch); // Вывод символа на экран
}

fclose(file);
return 0;
}

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

fgets()

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

#include <stdio.h>

int main() {
FILE *file = fopen("example.txt", "r");
if (file == NULL) {
perror("Ошибка при открытии файла");
return 1;
}

char buffer[100];
while (fgets(buffer, sizeof(buffer), file) != NULL) {
printf("%s", buffer); // Вывод строки на экран
}

fclose(file);
return 0;
}

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

fscanf()

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

#include <stdio.h>

int main() {
FILE *file = fopen("example.txt", "r");
if (file == NULL) {
perror("Ошибка при открытии файла");
return 1;
}

int number;
fscanf(file, "%d", &number);
printf("Прочитанное число: %d\n", number);

fclose(file);
return 0;
}

Запись данных в текстовые файлы

Запись данных в файлы осуществляется с помощью функций fputc(), fputs(), и fprintf().

fputc()

Функция fputc() записывает один символ в файл и возвращает записанный символ или EOF в случае ошибки.

#include <stdio.h>

int main() {
FILE *file = fopen("example.txt", "w");
if (file == NULL) {
perror("Ошибка при открытии файла");
return 1;
}

fputc('A', file);

fclose(file);
return 0;
}

fputs()

Функция fputs() записывает строку в файл и возвращает ненулевое значение в случае ошибки.

#include <stdio.h>

int main() {
FILE *file = fopen("example.txt", "w");
if (file == NULL) {
perror("Ошибка при открытии файла");
return 1;
}

fputs("Hello, World!\n", file);

fclose(file);
return 0;
}

fprintf()

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

#include <stdio.h>

int main() {
FILE *file = fopen("example.txt", "w");
if (file == NULL) {
perror("Ошибка при открытии файла");
return 1;
}

int number = 42;
fprintf(file, "Число: %d\n", number);

fclose(file);
return 0;
}

Бинарные файлы

Открытие и закрытие бинарных файлов

Для открытия файла в бинарном режиме к значению второго аргумента в функции fopen() приписывается буква b

  • wb - бинарный файл открывается для записи.
  • rb - бинарный файл открывается для чтения
  • ab - бинарный файл открывается для дозаписи
  • w+b - бинарный файл создается для записи/чтения
  • r+b - бинарный файл открывается для чтения/записи
  • a+b - бинарный файл открывается или создается (при его отсутствии) для чтения/дозаписи

Пример:

FILE *file;
file = fopen("example.bin", "wb");

Чтение данных из бинарных файлов

fread()

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

#include <stdio.h>

int main() {
FILE *file = fopen("example.bin", "rb");
if (file == NULL) {
perror("Ошибка при открытии файла");
return 1;
}

char buffer[100];
size_t bytesRead = fread(buffer, 1, sizeof(buffer), file);
printf("Прочитано %zu байт\n", bytesRead);

fclose(file);
return 0;
}

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

Запись данных в бинарные файлы

fwrite()

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

#include <stdio.h>

int main() {
FILE *file = fopen("example.bin", "wb");
if (file == NULL) {
perror("Ошибка при открытии файла");
return 1;
}

char buffer[100] = "Пример данных";
size_t bytesWritten = fwrite(buffer, 1, sizeof(buffer), file);
printf("Записано %zu байт\n", bytesWritten);

fclose(file);
return 0;
}

В примере записываются данные из буфера до 100 байт в бинарный файл и выводится количество записанных байт.

Важно !

Функции fread и fwrite обеспечивают высокую производительность за счет работы с буферами. Это позволяет минимизировать количество операций ввода-вывода, что особенно важно при работе с большими объемами данных. Например, вместо чтения или записи данных по одному байту, вы можете использовать буфер для обработки больших блоков данных за один раз.

Структуры и массивы в бинарных файлах

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

typedef struct {
int id;
char name[50];
float salary;
} Employee;

Employee emp = {1, "John Doe", 50000.0};
fwrite(&emp, sizeof(Employee), 1, file);

Для чтения структуры из файла:

Employee readEmp;
fread(&readEmp, sizeof(Employee), 1, file);

Аналогично можно работать и с массивами:

int array[5] = {1, 2, 3, 4, 5};
fwrite(array, sizeof(int), 5, file);

Чтение массива:

int readArray[5];
fread(readArray, sizeof(int), 5, file);

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

typedef struct {
int id;
char name[50];
float salary;
} Employee;

Employee employees[3] = {
{1, "Alice", 60000.0},
{2, "Bob", 55000.0},
{3, "Charlie", 70000.0}
};

FILE *file = fopen("employees.bin", "wb");
fwrite(employees, sizeof(Employee), 3, file);
fclose(file);

file = fopen("employees.bin", "rb");
Employee readEmployees[3];
fread(readEmployees, sizeof(Employee), 3, file);
fclose(file);

Этот пример демонстрирует, как можно записывать и читать массив структур. Массив employees содержит три структуры Employee, каждая из которых представляет собой запись о сотруднике. Запись массива в файл и последующее его чтение позволяет сохранить и восстановить данные о сотрудниках.

Позиционирование в файле

fseek()

Операция чтения-записи всегда производится с текущей позиции в потоке. При открытии потока в режимах r и w указатель текущей позиции устанавливается на начальный байт потока. При открытии в режиме a указатель устанавливается на конец файла сразу за конечным байтом. И при выполнении операции чтения-записи указатель в потоке перемещается на новую позиции в соответствии с числом прочитанных или записанных байтов. Если требуется считывать или записывать с какой-то определенной позиции, то используется функция fseek(), которая имеет следующий синтаксис:

int fseek(указатель_на_поток, смещение, начало_отсчета);
  • Второй параметр этой функции - смещение представляет числовое значение типа long, которое указывает, на какое количество байт надо переместить указатель. Это значение может быть отрицательным, если необходимо в потоке вернуться назад на некоторое количество байт.

  • Третий параметр - начало_отсчета задает начальную позицию, относительно которой идет смещение. В качестве этого параметра мы можем использовать одну из встроенных констант, определенных в файле stdio.h:

    SEEK_SET: имеет значение 0 и представляет начало файла

    SEEK_CUR: имеет значение 1 и представляет текущую позицию в потоке

    SEEK_END: имеет значение 2 и представляет конец файла

Если перемещение указателя было успешно выполнено, то функция fseek() возвращает 0, иначе она возвращает ненулевое значение.

Пример:

#include <stdio.h>

void load(char *, int);
void save(char *);
int main(void)
{
// файл для записи и чтения
char * filename = "data.txt";
// позиция, с которой начинается считывание
int position = 6;
save(filename);
load(filename, position);

return 0;
}
void load(char * filename, int position)
{
// буфер для считавания данных из файла
char buffer[256];
// чтение из файла
FILE *fp = fopen(filename, "r");
if(!fp)
{
printf("Error occured while opening file\n");
return;
}
// перемещаем указатель в файле на позицию position
fseek(fp, position, SEEK_SET);
// пока не дойдем до конца, считываем по 256 байт
while((fgets(buffer, 256, fp)))
{
printf("%s", buffer);
}

fclose(fp);
}
void save(char * filename)
{
// строка для записи
char * message = "Hello World!";
// запись в файл
FILE *fp = fopen(filename, "w");
if(!fp)
{
printf("Error occured while opening file\n");
return;
}
// записываем строку
fputs(message, fp);

fclose(fp);
printf("File has been written\n");
}

Поскольку в данном случае в качестве позиции передается число 6, то в тексте файла будут пропущены первые 6 символов, и будет считана подстрока "World!"

ftell()

Кроме функции fseek() существует еще пара функций для управления позицией указателя:

long ftell(FILE *): получает текущую позицию указателя

void rewind(FILE *): указатель устанавливается на начало потока

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

#include <stdio.h>

int main(void){

FILE* fp = fopen("test.txt", "r");
if(!fp) // если не удалось открыть файл
{
printf("Error while opening file\n");
return -1;
}
// если удалось, открыть файл получаем его длину
fseek(fp, 0, SEEK_END); // устанавливаем указатель на конец файл
long size = ftell(fp); // получаем значение указателя относительно начала
fclose(fp); // закрываем файл

printf("File size: %ld bytes\n", size);
}

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

fseek(fp, 0, SEEK_END);

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

long size = ftell(fp);

Особенности позиционирования в файле

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

Обработка ошибок при работе с файлами

Работа с файлами может сопровождаться различными ошибками, такими как невозможность открытия файла или ошибки чтения/записи. Для обработки ошибок используются функции perror() и feof().

perror()

Функция perror() выводит сообщение об ошибке на стандартный вывод ошибок. В качестве аргумента функция принимает строку, которая будет выведена перед сообщением об ошибке.

#include <stdio.h>

int main() {
FILE *file = fopen("nonexistent.txt", "r");
if (file == NULL) {
perror("Ошибка при открытии файла");
return 1;
}

fclose(file);
return 0;
}

В примере выше при попытке открыть несуществующий файл выводится сообщение об ошибке с помощью perror().

feof()

Функция feof() возвращает ненулевое значение, если достигнут конец файла

#include <stdio.h>

int main() {
FILE *file = fopen("example.txt", "r");
if (file == NULL) {
perror("Ошибка при открытии файла");
return 1;
}

int ch;
while ((ch = fgetc(file)) != EOF) {
putchar(ch);
}

if (feof(file)) {
printf("\nДостигнут конец файла\n");
}

fclose(file);
return 0;
}

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

Советы по работе с файлами

  • Всегда проверяйте возвращаемые значения функций для обнаружения ошибок.
  • Используйте отладочные сообщения для отслеживания выполнения программы.
  • Проверяйте правильность путей к файлам и прав доступа.
  • Используйте функции ferror() и clearerr() для проверки и сброса ошибок файловых операций.
  • Разделяйте логику работы с файлами на функции для улучшения читаемости и удобства отладки.
  • Используйте буферы: Чтение и запись данных блоками может значительно повысить производительность. Буферизация позволяет минимизировать количество операций ввода-вывода, что особенно важно при работе с большими файлами.
  • Проверяйте ошибки: Всегда проверяйте успешность выполнения операций с файлами. Это поможет избежать неожиданных сбоев и потери данных.
  • Используйте правильные режимы: Убедитесь, что используете правильные режимы открытия файлов (rb, wb, ab). Неправильный выбор режима может привести к потере данных или повреждению файла.
  • Работайте с указателями: Для работы с функциями fread() и fwrite() необходимо использовать указатели на данные. Это позволяет эффективно управлять памятью и данными.

Самостоятельная работа

Задачи

Задача 1

Дан текстовый файл in.txt, содержащий целые числа. Посчитать сумму чисел.

Решение
FILE *f;
int sum = 0, n;
f = fopen("in.txt", "r");
while (fscanf (f, "%d", &n) == 1)
sum += n;
fclose (f);
printf ("%d\n", sum);

Задача 2

Ввести имя файла и напечатать его размер, используя функцию ftell

Решение
FILE *f;
static char filename[100]={0};
size_t size;
printf("Input file name: ");
scanf("%s",filename);
f = fopen (filename, "r");
if (f != NULL) {
fseek (f, 0, SEEK_END);
size = ftell (f);
fclose (f);
printf ("File size of '%s' - %lu bytes.\n",filename, size);
}
else {
printf ("Can't open file %s\n", filename);
}

Задача 3

Дан текстовый файл in.txt. Необходимо посчитать количество цифр в файле и записать это число в конец данного файла.

Решение
FILE *f;
int sum = 0, n;
signed char c;// обязательно signed! иначе зациклится
f = fopen("in.txt", "r+"); // режим чтение и дозапись
while ( (c=fgetc(f))!=EOF ) {
if(c>='0' && c<='9') {
sum += c-'0';
}
}
fprintf (f, " %d", sum);
fclose (f);