
Чтобы работать с файлами, нужно создать файловый дескриптор (указатель на FILE). С помощью функции fopen() откроем файл, указав путь к нему и режим (не забудем проверить результат на равенство NULL), поработаем с ним и закроем с помощью fclose().
FILE *file = fopen("myfile.txt", "w+"); // Открываем файл.
if (file == NULL)
{
// Ошибка. Принимаем меры.
}
// Работаем с файлом.
fclose(file); // Закрываем файл.
Примечание. Некоторые компиляторы предлагают использовать безопасные версии старых функций (они имеют суффикс _s, например, fopen_s()), количество аргументов у них может быть другим. Чтобы использовать небезопасные функции, в самом начале файла можно объявить макрос #define _CRT_SECURE_NO_WARNINGS.
Часто приходится производить множество однородных действий (например, прочитать файл до конца), тогда пригодится такой цикл. Функция feof() говорит, достигнут ли конец файла.
while (!feof(file))
{
// ...
}
Теперь рассмотрим режимы открытия файлов.
Для чтения и записи можно воспользоваться функциями fprintf() и fscanf(), которые похожи на printf() и scanf().
fprintf(file, "%d", 42);
int foo = 0;
fscanf(file, "%d", &foo);
С помощью функций fgetc() и fputc() можно считывать из файла один символ и записывать символ в файл.
char symbol = fgetc(file);
fputc('q', file);
Функции fgets() и fputs() считывают и записывают строки в файл. Второй аргумент при считывании строки — размер буфера со строкой.
#define BUFFER_SIZE 256
char buffer[BUFFER_SIZE];
fgets(buffer, BUFFER_SIZE, file); // Считать строку.
// ...
fputs(buffer, file); // Записать строку.
С помощью функции rewind() можно возвращаться к началу файла. Давайте представим, что мы записали в файл некоторое число с помощью fprintf(), затем вернулись в начало файла.
fprintf(file, "%d", 5318008);
rewind(file); // Устанавливаем позицию на начало.
fprintf(file, "%c%c%c", 33, 33, 33);
Каким будет содержимое файла? !!!8008 или !!!5318008? Или что-то ещё? Правильный ответ — !!!8008.
Для перемещения внутри файла вперёд и назад используются fpos_t для хранения позиции и функции fgetpos() и fsetpos().
fpos_t position;
fgetpos(file, &position); // Получить позицию.
fsetpos(file, &position); // Установить позицию.
Альтернативный способ — функции ftell() и fseek(). Третий аргумент функции fseek() — SEEK_SET (отсчёт от начала файла), SEEK_CUR (отсчёт от текущей позиции) либо SEEK_END (отсчёт от конца, может не поддерживаться некоторыми компиляторами).
long position = ftell(file); // Получить позицию.
fseek(file, position, SEEK_SET); // Установить позицию.
Бинарный и текстовый режимы — это вопрос интерпретации данных. В бинарном режиме данные выводятся как есть, байт за байтом. Для этого существуют функции fwrite() и fread().
struct Foo foo[3];
// ...
fwrite(&foo, sizeof(struct Foo), sizeof(foo), file);
fread(&foo, sizeof(struct Foo), sizeof(foo), file);
Первый аргумент обеих функций — адрес буфера (данных), второй — размер одного элемента (в нашем случае — структуры), третий — количество таких элементов, с четвёртым уже должно быть всё ясно. Работать таким образом можно не только со структурой, но и с обычными переменными и массивами.
В текстовом режиме (например, fprintf()) целые числа (int) будут выводиться как есть. В таблице вторая и третья графы — двоичное и шестнадцатеричное представления (уже после записи в файл). А последняя графа — то, как может выглядеть результат, когда вы откроете файл в текстовом редакторе.
В бинарном режиме (fwrite()) в файл будет записываться двоичное представление переменной — байт за байтом. Мы уже не увидим в файле девятки.
Но это вариант для архитектур с прямым порядком байтов (big-endianness), на практике вы скорее столкнётесь с обратным порядком байтов (little-endianness).
Текстовые форматы менее компактны, но зато совместимы между разными платформами.
Перенаправляя вывод, можно сделать много интересного. Мы лишь покажем, как перенаправлять стандартный поток ошибок stderr в файл.
freopen("errors.txt", "w", stderr);
perror("Error"); // Сообщение запишется в файл.