Глава 10: Работа с файлами ========================== В этой главе мы рассмотрим основы работы с файлами в языке C. Работа с файлами позволяет сохранять и считывать данные вне оперативной памяти, делая программы более полезными и долговечными. Мы изучим: * Открытие файлов (`fopen`) * Чтение и запись (`fread`, `fwrite`, `fprintf`, `fscanf`) * Закрытие файлов (`fclose`) * Проверку ошибок при работе с файлами * Примеры чтения и записи текстовых и бинарных файлов --- Работа с файлами в C — это фундаментальный навык, который позволяет вашим программам взаимодействовать с внешним миром. Когда вы запускаете программу, все её данные хранятся в **оперативной памяти (RAM)**. Как только программа завершает работу, эти данные исчезают. Файлы предоставляют способ **постоянного хранения данных** на диске, чтобы они сохранялись между запусками программы или могли быть доступны другим программам. По сути, файл — это просто упорядоченная последовательность байтов. Однако, когда мы говорим о файлах в контексте программирования, мы часто различаем **текстовые файлы** и **бинарные файлы**. * **Текстовые файлы** содержат символы, которые человек может прочитать, например, обычный текст. Каждый символ хранится как его соответствующее кодирование (например, ASCII или UTF-8). * **Бинарные файлы** хранят данные в том виде, в котором они представлены в памяти компьютера (например, целые числа, структуры). Эти файлы обычно не предназначены для чтения человеком напрямую и требуют специального программного обеспечения для интерпретации. Стандартная библиотека ввода/вывода C (`stdio.h`) предоставляет набор функций для работы с файлами. Все эти функции оперируют с указателями на тип `FILE`, который представляет собой абстракцию файла. --- ### Основные функции для работы с файлами #### Открытие файлов: `fopen()` Прежде чем вы сможете читать из файла или записывать в него, вам нужно его **открыть**. Для этого используется функция `fopen()`. .. code-block:: c FILE *fopen(const char *filename, const char *mode); * ``filename``: Строка, содержащая имя файла, который вы хотите открыть. * ``mode``: Строка, указывающая режим открытия файла. .. list-table:: Режимы открытия файлов :widths: 15 85 :header-rows: 1 * - Режим - Описание * - ``"r"`` - Открыть для чтения. Файл должен существовать. * - ``"w"`` - Открыть для записи. Если файл существует, его содержимое **удаляется**. Если нет, файл создается. * - ``"a"`` - Открыть для добавления (дозаписи). Запись происходит в конец файла. Если файл не существует, он создается. * - ``"rb"`` - Открыть для чтения в бинарном режиме. * - ``"wb"`` - Открыть для записи в бинарном режиме. Если файл существует, его содержимое **удаляется**. Если нет, файл создается. * - ``"ab"`` - Открыть для добавления в бинарном режиме. Запись происходит в конец файла. Если файл не существует, он создается. * - ``"r+"`` - Открыть для чтения и записи. Файл должен существовать. * - ``"w+"`` - Открыть для чтения и записи. Если файл существует, его содержимое **удаляется**. Если нет, файл создается. * - ``"a+"`` - Открыть для чтения и добавления. Запись происходит в конец файла. Если файл не существует, он создается. Функция ``fopen()`` возвращает указатель на ``FILE``, который будет использоваться во всех последующих операциях с этим файлом. Если файл не может быть открыт (например, из-за отсутствия прав, или файл не существует в режиме ``"r"``), ``fopen()`` возвращает ``NULL``. **Всегда проверяйте возвращаемое значение ``fopen()``!** #### Закрытие файлов: `fclose()` Когда вы закончили работу с файлом, очень важно его **закрыть**. Функция ``fclose()`` освобождает ресурсы, связанные с файлом, и гарантирует, что все буферизованные данные будут записаны на диск. .. code-block:: c 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``. * Возвращает количество успешно считанных элементов. Эти функции очень мощные, так как позволяют читать и записывать целые структуры данных или массивы за один вызов, что эффективно для бинарных данных. --- Пример 1: Запись строки в файл ------------------------------ Название файла: ``write_to_file.c`` .. code-block:: c #include // Подключаем стандартную библиотеку ввода/вывода для работы с файлами. 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, чтобы указать на успешное завершение программы. } --- Пример 2: Чтение файла построчно -------------------------------- Название файла: ``read_line_by_line.c`` .. code-block:: c #include // Подключаем стандартную библиотеку ввода/вывода. 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; // Успешное завершение. } --- Пример 3: Запись и чтение бинарного файла ----------------------------------------- Название файла: ``binary_file_rw.c`` .. code-block:: c #include // Подключаем стандартную библиотеку ввода/вывода. 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; // Успешное завершение программы. } --- Дополнительные ресурсы ---------------------- * Практическое руководство: https://en.cppreference.com/w/c/io