§ 10. Работа с файлами

§ 10.1 Режимы работы и основы

Чтобы работать с файлами, нужно создать файловый дескриптор (указатель на 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))
{
    // ...
}

Теперь рассмотрим режимы открытия файлов.

Режим

Описание

"r"

Открыть для чтения (файл должен существовать).

"w"

Создать пустой файл для записи. Если файл уже существует, его содержимое будет потеряно.

"a"

Открыть для дописывания в конец файла. Если файл не существовал, он будет создан.

"r+"

Открыть для чтения и записи. Файл должен существовать.

"w+"

Создать новый файл для чтения и записи.

"a+"

Открыть файл для чтения и дописывания в конец файла.

Для чтения и записи можно воспользоваться функциями 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); // Записать строку.

§ 10.2 Позиция внутри файла

С помощью функции 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); // Установить позицию.

§ 10.3 Бинарный режим

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

struct Foo foo[3];
// ...
fwrite(&foo, sizeof(struct Foo), sizeof(foo), file);
fread(&foo, sizeof(struct Foo), sizeof(foo), file);

Первый аргумент обеих функций — адрес буфера (данных), второй — размер одного элемента (в нашем случае — структуры), третий — количество таких элементов, с четвёртым уже должно быть всё ясно. Работать таким образом можно не только со структурой, но и с обычными переменными и массивами.

В текстовом режиме (например, fprintf()) целые числа (int) будут выводиться как есть. В таблице вторая и третья графы — двоичное и шестнадцатеричное представления (уже после записи в файл). А последняя графа — то, как может выглядеть результат, когда вы откроете файл в текстовом редакторе.

Значение

BIN

HEX

В файле

9

00111001

39

9

99

00111001 00111001

39 39

99

В бинарном режиме (fwrite()) в файл будет записываться двоичное представление переменной — байт за байтом. Мы уже не увидим в файле девятки.

Значение

BIN

HEX

В файле

9

00000000 00000000 00000000 00001001

00 00 00 09

NULNULNULTAB

99

00000000 00000000 00000000 01100011

00 00 00 63

NULNULNULc

Но это вариант для архитектур с прямым порядком байтов (big-endianness), на практике вы скорее столкнётесь с обратным порядком байтов (little-endianness).

Значение

BIN

HEX

В файле

9

00001001 00000000 00000000 00000000

09 00 00 00

TABNULNULNUL

99

01100011 00000000 00000000 00000000

63 00 00 00

cNULNULNUL

Текстовые форматы менее компактны, но зато совместимы между разными платформами.

§ 10.4 Перенаправление потока

Перенаправляя вывод, можно сделать много интересного. Мы лишь покажем, как перенаправлять стандартный поток ошибок stderr в файл.

freopen("errors.txt", "w", stderr);
perror("Error"); // Сообщение запишется в файл.
14 июня