10. Глава 10: Работа с файлами
В этой главе мы рассмотрим основы работы с файлами в языке C. Работа с файлами позволяет сохранять и считывать данные вне оперативной памяти, делая программы более полезными и долговечными.
Мы изучим:
Открытие файлов (fopen)
Чтение и запись (fread, fwrite, fprintf, fscanf)
Закрытие файлов (fclose)
Проверку ошибок при работе с файлами
Примеры чтения и записи текстовых и бинарных файлов
—
Работа с файлами в C — это фундаментальный навык, который позволяет вашим программам взаимодействовать с внешним миром. Когда вы запускаете программу, все её данные хранятся в оперативной памяти (RAM). Как только программа завершает работу, эти данные исчезают. Файлы предоставляют способ постоянного хранения данных на диске, чтобы они сохранялись между запусками программы или могли быть доступны другим программам.
По сути, файл — это просто упорядоченная последовательность байтов. Однако, когда мы говорим о файлах в контексте программирования, мы часто различаем текстовые файлы и бинарные файлы.
Текстовые файлы содержат символы, которые человек может прочитать, например, обычный текст. Каждый символ хранится как его соответствующее кодирование (например, ASCII или UTF-8).
Бинарные файлы хранят данные в том виде, в котором они представлены в памяти компьютера (например, целые числа, структуры). Эти файлы обычно не предназначены для чтения человеком напрямую и требуют специального программного обеспечения для интерпретации.
Стандартная библиотека ввода/вывода C (stdio.h) предоставляет набор функций для работы с файлами. Все эти функции оперируют с указателями на тип FILE, который представляет собой абстракцию файла.
—
### Основные функции для работы с файлами
#### Открытие файлов: fopen()
Прежде чем вы сможете читать из файла или записывать в него, вам нужно его открыть. Для этого используется функция fopen().
FILE *fopen(const char *filename, const char *mode);
filename
: Строка, содержащая имя файла, который вы хотите открыть.mode
: Строка, указывающая режим открытия файла.
Режим |
Описание |
---|---|
|
Открыть для чтения. Файл должен существовать. |
|
Открыть для записи. Если файл существует, его содержимое удаляется. Если нет, файл создается. |
|
Открыть для добавления (дозаписи). Запись происходит в конец файла. Если файл не существует, он создается. |
|
Открыть для чтения в бинарном режиме. |
|
Открыть для записи в бинарном режиме. Если файл существует, его содержимое удаляется. Если нет, файл создается. |
|
Открыть для добавления в бинарном режиме. Запись происходит в конец файла. Если файл не существует, он создается. |
|
Открыть для чтения и записи. Файл должен существовать. |
|
Открыть для чтения и записи. Если файл существует, его содержимое удаляется. Если нет, файл создается. |
|
Открыть для чтения и добавления. Запись происходит в конец файла. Если файл не существует, он создается. |
Функция fopen()
возвращает указатель на FILE
, который будет использоваться во всех последующих операциях с этим файлом. Если файл не может быть открыт (например, из-за отсутствия прав, или файл не существует в режиме "r"
), fopen()
возвращает NULL
. Всегда проверяйте возвращаемое значение ``fopen()``!
#### Закрытие файлов: fclose()
Когда вы закончили работу с файлом, очень важно его закрыть. Функция fclose()
освобождает ресурсы, связанные с файлом, и гарантирует, что все буферизованные данные будут записаны на диск.
int fclose(FILE *fp);
fp
: Указатель наFILE
, возвращенныйfopen()
.
fclose()
возвращает 0
в случае успешного закрытия или EOF
(End Of File) в случае ошибки. Не закрытые файлы могут привести к потере данных или ошибкам в программе.
#### Чтение и запись текстовых данных
Для работы с текстовыми файлами часто используются функции, похожие на printf()
и scanf()
, но с добавлением указателя на файл:
fprintf(FILE *fp, const char *format, ...)
: Записывает форматированные данные в файл. Аналогичнаprintf()
, но пишет в файл.fscanf(FILE *fp, const char *format, ...)
: Считывает форматированные данные из файла. Аналогичнаscanf()
, но читает из файла.fgetc(FILE *fp)
: Считывает один символ из файла.fputc(int char_val, FILE *fp)
: Записывает один символ в файл.fgets(char *buffer, int size, FILE *fp)
: Считывает строку из файла. Читает доsize-1
символов или до символа новой строки, или до конца файла. ВозвращаетNULL
при ошибке или достижении конца файла.fputs(const char *str, FILE *fp)
: Записывает строку в файл.
#### Чтение и запись бинарных данных
Для работы с бинарными файлами используются функции fread()
и fwrite()
:
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *fp);
ptr
: Указатель на массив или переменную, данные которой нужно записать.size
: Размер одного элемента в байтах (частоsizeof(тип)
).nmemb
: Количество элементов, которые нужно записать.fp
: Указатель наFILE
.Возвращает количество успешно записанных элементов.
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *fp);
ptr
: Указатель на буфер, куда будут считаны данные.size
: Размер одного элемента в байтах.nmemb
: Максимальное количество элементов, которое нужно считать.fp
: Указатель наFILE
.Возвращает количество успешно считанных элементов.
Эти функции очень мощные, так как позволяют читать и записывать целые структуры данных или массивы за один вызов, что эффективно для бинарных данных.
—
10.1. Пример 1: Запись строки в файл
Название файла: write_to_file.c
#include <stdio.h> // Подключаем стандартную библиотеку ввода/вывода для работы с файлами.
int main() {
// Объявляем указатель на FILE. Это будет наш "дескриптор" файла.
FILE *fp = fopen("output.txt", "w"); // Открываем файл "output.txt" в режиме записи ("w").
// Если файла нет, он будет создан. Если есть, его содержимое будет перезаписано.
// Проверяем, удалось ли открыть файл. fopen возвращает NULL в случае ошибки.
if (fp == NULL) {
printf("Не удалось открыть файл для записи.\n"); // Выводим сообщение об ошибке.
return 1; // Возвращаем ненулевой код, чтобы указать на ошибку.
}
// Записываем строку "Привет, файл!\n" в открытый файл.
// fprintf работает аналогично printf, но первым аргументом принимает указатель на файл.
fprintf(fp, "Привет, файл!\n");
// Закрываем файл. Это очень важный шаг, чтобы сохранить изменения и освободить ресурсы.
fclose(fp);
printf("Строка успешно записана в output.txt\n"); // Сообщаем пользователю об успехе.
return 0; // Возвращаем 0, чтобы указать на успешное завершение программы.
}
—
10.2. Пример 2: Чтение файла построчно
Название файла: read_line_by_line.c
#include <stdio.h> // Подключаем стандартную библиотеку ввода/вывода.
int main() {
// Открываем файл "output.txt" в режиме чтения ("r").
// Этот файл был создан предыдущей программой.
FILE *fp = fopen("output.txt", "r");
// Проверяем, удалось ли открыть файл.
if (fp == NULL) {
printf("Не удалось открыть файл для чтения. Убедитесь, что 'output.txt' существует.\n");
return 1; // Код ошибки.
}
char buffer[256]; // Объявляем буфер (массив символов) для хранения считанной строки.
// Размер 256 байт означает, что мы можем прочитать строку до 255 символов + завершающий NULL.
printf("Чтение содержимого файла 'output.txt':\n");
// Используем цикл while и функцию fgets для чтения файла построчно.
// fgets(буфер, размер_буфера, указатель_на_файл)
// Она читает строку из файла до символа новой строки, или до достижения конца файла,
// или до того, как будет считано (размер_буфера - 1) символов.
// Возвращает NULL при достижении конца файла или ошибке.
while (fgets(buffer, sizeof(buffer), fp)) {
printf("Считано: %s", buffer); // Выводим считанную строку на консоль.
// fgets включает символ новой строки '\n' (если он есть в файле),
// поэтому здесь не нужно добавлять свой '\n'.
}
// Закрываем файл после завершения чтения.
fclose(fp);
printf("Чтение файла завершено.\n");
return 0; // Успешное завершение.
}
—
10.3. Пример 3: Запись и чтение бинарного файла
Название файла: binary_file_rw.c
#include <stdio.h> // Подключаем стандартную библиотеку ввода/вывода.
int main() {
// --- Часть 1: Запись в бинарный файл ---
int numbers[] = {1, 2, 3, 4, 5}; // Объявляем массив целых чисел для записи.
// Открываем файл "numbers.bin" в режиме бинарной записи ("wb").
// 'w' - запись, 'b' - бинарный режим.
FILE *fp = fopen("numbers.bin", "wb");
if (fp) { // Проверяем, успешно ли открыт файл.
// Записываем массив чисел в файл.
// fwrite(указатель_на_данные, размер_одного_элемента, количество_элементов, указатель_на_файл)
// sizeof(int) определяет размер одного целого числа в байтах.
// 5 - это количество элементов в массиве numbers.
fwrite(numbers, sizeof(int), 5, fp);
fclose(fp); // Закрываем файл.
printf("Массив чисел успешно записан в numbers.bin\n");
} else {
printf("Ошибка: Не удалось открыть файл 'numbers.bin' для записи.\n");
return 1; // Код ошибки.
}
// --- Часть 2: Чтение из бинарного файла ---
int read_numbers[5]; // Объявляем массив для хранения чисел, считанных из файла.
// Открываем тот же файл "numbers.bin" в режиме бинарного чтения ("rb").
fp = fopen("numbers.bin", "rb");
if (fp) { // Проверяем, успешно ли открыт файл.
// Считываем данные из файла в массив read_numbers.
// fread(указатель_на_буфер, размер_одного_элемента, количество_элементов, указатель_на_файл)
// Здесь мы ожидаем 5 целых чисел.
fread(read_numbers, sizeof(int), 5, fp);
fclose(fp); // Закрываем файл.
printf("Числа, считанные из 'numbers.bin': ");
// Выводим считанные числа на консоль.
for (int i = 0; i < 5; i++) {
printf("%d ", read_numbers[i]);
}
printf("\n");
} else {
printf("Ошибка: Не удалось открыть файл 'numbers.bin' для чтения.\n");
return 1; // Код ошибки.
}
return 0; // Успешное завершение программы.
}
—
10.4. Дополнительные ресурсы
Практическое руководство: https://en.cppreference.com/w/c/io