Встроенные функции fwrite() и fread() упрощают запись и чтение из файлов сложных данных. Рассмотрим их применение.
Функция 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 предназначена для считывания данных из файла в массив. Она имеет следующее определение:
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, то выводим эту структуру на консоль.