Чтение и запись в файл с помощью функций fwrite и fread

Последнее обновление: 29.04.2023

Встроенные функции fwrite() и fread() упрощают запись и чтение из файлов сложных данных. Рассмотрим их применение.

fwrite

Функция fwrite предназначена для записи данных из массива. Она имеет следующее определение:

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

Эта функция принимает следующие параметры:

  • buffer: указатель на первый объект из массива, который должен быть записан

  • size: размер каждого объекта

  • count: количество объектов, которые надо записать

  • stream указатель на файловый поток для записи

В качестве результата функция возвращает количество записанных объектов. Это количество может быть меньше параметра count, если при записи произогла ошибка

Фактически функция записывает каждый объект как массив значений unsigned char, вызывая при записи для каждого значения unsigned char функцию fputc.

Для простейшего примера запишем строку:

#include <stdio.h>

int main() {
    char str[] = "Hello METANIT.COM";
    size_t N = sizeof(str);
    FILE *fp = fopen("data.txt", "w");
    size_t count = fwrite(str, sizeof str[0], N, fp);
    printf("wrote %zu elements out of %zu\n", count,  N);
    fclose(fp);
}

Здесь записываем строку str в файл "data.txt". Для получения длины строки используем выражение sizeof(str), а для получения размера одного символа - sizeof str[0] (в приниципе здесь можно сразу написать 1 - размер одного символа). И если запись пройдет успешно, то консоль выведет

wrote 18 elements out of 18

fread

Функция fread предназначена для считывания данных из файла в массив. Она имеет следующее определение:

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

Эта функция принимает следующие параметры:

  • buffer: указатель на массив, в который надо считать данные

  • size: размер каждого объекта

  • count: количество объектов, которые надо считать

  • stream указатель на файловый поток для чтения

В качестве результата функция возвращает количество считанных объектов. Это количество может быть меньше параметра count, если при записи произогла ошибка или достигнут конец файла.

Фактически функция считывает данные с помощью функции fgetc() в буфер, который рассматривается как массив значений unsigned char.

Для простейшего примера считаем строку из вышезаписанного файла:

#include <stdio.h>

int main() {
    int N  = 20;
    char str[N];   // определяем буфер достаточной длины

    FILE *fp = fopen("data.txt", "r");
    size_t count = fread(str, sizeof str[0], N, fp); 
    printf("read %zu elements out of %d\n", count,  N);
    printf(str);
    fclose(fp);
}

Здесь считываем данные в строку str. В итоге консольный вывод будет следующим:

read 18 elements out of 20
Hello METANIT.COM

Отслеживание ошибок

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

#include <stdio.h>

int main() {
    int N  = 20;
    char str[N];   // определяем буфер достаточной длины

    FILE *fp = fopen("data.txt", "r");
    int count = fread(str, sizeof str[0], N, fp);
    if(count == N)
    {
        printf("%s \n", str);
    }
    else
    {
        if (feof(fp))   // если конец файла
        {
            printf("Unexpected end of file\n");
            printf("Available: %s",str);
        }
        else if (ferror(fp))    // если ошибка
            perror("Error while reading file\n");
    }
    fclose(fp);
}

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

Запись и чтение структур

Запись и чтение строки - не самая сложная задача, которую можно выполнить и другими способами. Но функции fwrite и fread позволяют работать и с другими объектами, даже если они и не представляют массив. Поскольку функции в качестве буфера принимают указатель, но в качестве указателя может выступать адрес любого объекта. Рассмотрим, как записать и считать структуры. Для этого определим следующую программу:

#include <stdio.h>

struct person
{
    char name[20];
    int age;
};

int main() {
    // запись файла
    struct person tom = {"Tom", 22}; // структура для записи
    int size = sizeof(struct person);
    FILE *fp = fopen("person.bin", "w");
    // записываем одну структуру
    size_t count = fwrite(&tom, size, 1, fp); 
    printf("wrote %zu elements out of %d\n", count,  1);
    fclose(fp);

    // считывание структуры
    struct person unknown;  // структура для чтения
    fp = fopen("person.bin", "r");
    // считываем одну структуру
    count = fread(&unknown, size, 1, fp); 
    if(count == 1)
    {  
        printf("Name: %s \n", unknown.name);
        printf("Age: %d \n", unknown.age);
    }
    fclose(fp);
}

Здесь записываем структуру типа person. Для этого в функцию fwrite() передаем адрес структуры:

fwrite(&tom, size, 1, fp);

После записи считываем данные в структуру unknown, для чего передаем в функцию fread() адрес этой структуры:

fread(&unknown, size, 1, fp);

Запись-чтение массива структур

Подобным образом можно записывать и считывать массив структур. Для этого определим следующую программу:

#include <stdio.h>

struct person
{
    char name[20];
    int age;
};

int main() {
    char * filename = "people.bin";
    
    // массив для записи
    struct person people[] = { {"Tom", 23}, {"Alice", 27}, {"Bob", 31}, {"Kate", 29 }};
    int size = sizeof(people[0]);              // размер всего массива
    int count = sizeof(people)  / size;         // количество структур 
    
    // запись файла
    FILE *fp = fopen(filename, "w");
    // записываем массив структур
    size_t written = fwrite(people, size, count, fp); 
    printf("wrote %zu elements out of %d\n", written, count);
    fclose(fp);

    // считывание файла
    struct person users[count];  // массив для чтения структур
    fp = fopen(filename, "r");
    size_t read = fread(users, size, count, fp); 
    printf("read %zu elements\n", read);

    if(read > 0)
    {  
        for(int i = 0; i < count; i++)
        {
            printf("Name: %s \t Age: %d\n", users[i].name, users[i].age);
        }
    }
    fclose(fp);
}

В данном случае записываем в файл массив структур people, а считываем - в массив users. Консольный вывод программы:

wrote 4 elements out of 4
read 4 elements
Name: Tom        Age: 23
Name: Alice      Age: 27
Name: Bob        Age: 31
Name: Kate       Age: 29

Считывание по одной структуре

В примерах выше разом считывались те данные, которые ранее были записаны. Но допустим, нам нужно вывести на консоль не всех пользователей, а каких-то определенных. Например, тех, которые имеют определенное имя или возраст. Мы опять же могли бы считать весь массив и затем пройтись по нему в цикле, находя в нем нужные данные. Но также мы можем считывать непосредственно из файла по одному объекту и проверять его. Например, определим следующую программу:

#include <stdio.h>

struct person
{
    char name[20];
    int age;
};

void save(char*);
void read_by_age(char*, int);

int main() {
    char * filename = "people.bin";
    save(filename);
    read_by_age(filename, 22);   // находим пользователей, которым 22 года
}

// записываем данные
void save(char* filename)
{
    struct person people[] = { {"Tom", 22}, {"Bob", 33}, {"Kate", 33 }, {"Alice", 22}};
    int size = sizeof(people[0]);              // размер всего массива
    int count = sizeof(people)  / size;         // количество структур 

    FILE *fp = fopen(filename, "w");
    size_t written = fwrite(people, size, count, fp); 
    printf("wrote %zu elements out of %d\n", written, count);
    fclose(fp);
}
// выводим данные на консоль для возраста age
void read_by_age(char* filename, int age)
{
    struct person p;  // структур для считывания файла
    FILE* fp = fopen(filename, "r");
    while(fread(&p, sizeof(p), 1, fp) == 1)
    {
        if(p.age == age)    // если пользователь имеет определенный возраст
        {
            printf("Name: %s \t Age: %d \n", p.name, p.age);
        }
    }
    fclose(fp);
}

Здесь запись и чтение разбиты на отдельные функции. Но если в функции load() записываем в файл разом весь массив структур, то в функции read_by_age() считываем по одной структуре в цикле, пока в файле есть данные. И если поле age структуры соответствует параметру age, то выводим эту структуру на консоль.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850