Открытие и закрытие файлов

Ранее мы использовали стандартные потоки — клавиатуру и монитор для ввода-вывода данных. Теперь познакомимся с тем, как в языке C реализуется работа с файлами, ведь перед чтением или записью данных необходимо сначала открыть файл, чтобы получить к нему доступ.

В языке C указатель на файл обозначается типом FILE, и его объявление выглядит следующим образом:
FILE *myfile;

Используя функцию fopen(), можно открыть файл, предоставив адрес файла в качестве первого аргумента. Это можно сделать в режиме чтения ("r"), записи ("w") или добавления ("a"). Функция возвращает указатель на открытый файл, и поэтому открытие файла обычно оформляется так:
myfile = fopen ("hello.txt", "r");

Все операции чтения и записи в файл осуществляются через файловый указатель, в нашем случае - это myfile.

Если функция fopen() не сможет открыть файл из-за отсутствия файла по указанному адресу или запрета доступа, она вернет NULL. В большинстве программ подобные ошибки обрабатываются внутри if, но мы сейчас опустим это.

Заголовочный файл stdio.h содержит объявление функции fopen() и описывает тип-структуру FILE, поэтому требуется его подключение.

После завершения работы с файлом рекомендуется его закрывать. Это позволяет освободить буфер данных и по другим причинам. Особенно важно это делать, если программа продолжает работу после взаимодействия с файлом. Закрытие файла и отключение указателя от него осуществляется с помощью функции fclose(). Ей передается указатель на файл:
fclose(myfile);

В одной программе может быть открыто несколько файлов одновременно. Каждый из них должен быть связан с собственным файловым указателем. Однако, если один файл уже закрыт, указатель может быть использован для открытия нового файла.

Чтение из текстового файла и запись в него

fscanf()

Функция fscanf() работает аналогично scanf(), но предназначена для форматированного ввода из файла, а не из стандартного ввода. Параметры функции fscanf() включают файловый указатель, строку формата и адреса памяти для сохранения данных:
fscanf (myfile, "%s%d", str, &a);

Она возвращает количество успешно считанных данных или EOF. Пробелы и символы новой строки интерпретируются как разделители.

Допустим, файл содержит следующие данные об объектах:

apples 10 23.4
bananas 5 25.0
bread 1 10.3

Пример программы для чтения данных:

#include <stdio.h>
 
main () {
    FILE *file;
    struct food {
        char name[20];
        unsigned qty;
        float price;
    };
    struct food shop[10];
    char i=0;
 
    file = fopen("fscanf.txt", "r");
 
    while (fscanf (file, "%s%u%f", shop[i].name, &(shop[i].qty), &(shop[i].price)) != EOF) {
        printf("%s %u %.2f\n", shop[i].name, shop[i].qty, shop[i].price);
        i++;
    }
}

В этом фрагменте программы объявляется структура и массив структур. Каждая строка файла соответствует элементу массива, который представляет собой структуру с строковым и двумя числовыми полями. Цикл производит одну итерацию для считывания строки, и при встрече EOF fscanf() прекращает цикл.

fgets()

Функция fgets() похожа на gets() и используется для построчного ввода из файла. Каждое применение fgets() стремится считать одну строку. Можно указать чтение лишь части строки, начиная с её начала. Параметры функции следующие:
fgets (массив_символов, количество_символов, файловый_указатель)

Пример:
fgets (str, 50, myfile)

Этот пример сканирует строку, связанную с указателем myfile. Если строка меньше 50 символов, она будет прочтена полностью, включая '\n', который сохранится в массиве, но последним (50-ым) символом будет '\0', добавленный fgets(). Если строка длиннее, читается 49 символов, и '\0' помещается в конец строки, а '\n' будет отсутствовать.

#include <stdio.h>
 
#define N 80
 
main () {
    FILE *file;
    char arr[N];
 
    file = fopen("fscanf.txt", "r");
 
    while (fgets (arr, N, file) != NULL)
        printf("%s", arr);
 
    printf("\n");
    fclose(file);
}

В этом примере программа считывает данные строками в массив arr. Когда новая строка считывается, предыдущая перезаписывается. Как только fgets() не может считать строку, она возвращает NULL.

getc() или fgetc()

Функции getc() и fgetc() обе позволяют извлечь один символ из файла.

    while ((arr[i] = fgetc (file)) != EOF) {
        if (arr[i] == '\n') {
            arr[i] = '\0';
            printf("%s\n",arr);
            i = 0;
        }
        else i++;
    }
    arr[i] = '\0';
    printf("%s\n",arr);

Представленный код считывает символы из файла и выводит их на экран.

Запись в текстовый файл

Процесс записи в файл может быть разным, как и ввод.

  • Форматированный вывод осуществляется функцией fprintf (файловый_указатель, строка_формата, переменные).
  • Построчная запись происходит с помощью функции fputs (строка, файловый_указатель).
  • Запись по символам поддерживается функцией fputc() или с помощью putc(символ, файловый_указатель).

Примеры использования функций для записи данных в файл:

Запись каждой структуры в отдельной строке файла:

    file = fopen("fprintf.txt", "w");
 
    while (scanf ("%s%u%f", shop[i].name, &(shop[i].qty), &(shop[i].price)) != EOF) {
        fprintf(file, "%s %u %.2f\n", shop[i].name, shop[i].qty, shop[i].price);
        i++;
    }

Построчная запись в файл (функция fputs() сама не добавляет '\n' в конце):

    while (gets (arr) != NULL) {
        fputs(arr, file);
        fputs("\n", file);
    }

Пример посимвольной записи:

    while ((i = getchar()) != EOF)
        putc(i, file);

Чтение из двоичного файла и запись в него

В работе с файлами можно перешагнуть за текст и обращаться к файлу как к последовательности байтов. Такое обращение необходимо для работы с бинарными данными. Однако, это применимо и для работы с текстовыми файлами. Основное преимущество бинарного доступа заключается в ускоренной записи-чтении, так как за раз можно обработать большой блок данных.

Для открытия файла в двоичном режиме используется строка "rb" или "wb" в качестве второго параметра функции fopen().

Работа с бинарными файлами является сложной темой и требует отдельного занятия. Здесь же будут указаны особенности функций для чтения-записи в файл как в поток байтов.

Функции fread() и fwrite() принимают параметры:

  1. адрес памяти, куда записываются или откуда считываются данные,
  2. размер одного элемента определенного типа,
  3. число элементов данных для операции,
  4. файловый указатель.

Эти функции возвращают количество удачно обработанных элементов. К примеру, вы можете запросить чтение 50 элементов, но получить только 10, без ошибки.

Пример использования fread() и fwrite():

#include <stdio.h>
#include <string.h>
 
main () {
    FILE *file;
    char shelf1[50], shelf2[100];
    int n, m;
 
    file = fopen("shelf1.txt", "rb");
    n=fread(shelf1, sizeof(char), 50, file);
    fclose(file);
 
    file = fopen("shelf2.txt", "rb");
    m=fread(shelf2, sizeof(char), 50, file);
    fclose(file);
 
    shelf1[n] = '\0';
    shelf2[m] = '\n';
    shelf2[m+1] = '\0';
 
    file = fopen("shop.txt", "wb");
    fwrite(strcat(shelf2,shelf1), sizeof(char), n+m, file);
    fclose(file);
}

Здесь происходит попытка считать 50 символов из первого файла. В переменной n сохраняется количество реально считанных символов, которое может быть 50 или меньше. Данные сохраняются в строковой переменной. Аналогичное действие выполняется для второго файла. Далее первая строка присоединяется ко второй и записывается в третий файл.

Решение задач

  1. Создайте программу, которая будет запрашивать у пользователя имя (адрес) текстового файла, открывать его и подсчитывать количество символов и строк.
  2. Разработайте программу, записывающую данные из одного файла в другой с возможностью изменения данных перед записью. Каждая строка из исходного файла должна быть представлена в виде структуры.

Вопросы для самопроверки:

  1. Какой тип используется для указателя на файл в языке C?
  2. Какие режимы открытия файла можно указать в функции fopen()?
  3. Что возвращает функция fopen() при успешном открытии файла, а что в случае ошибки?
  4. Для чего рекомендуется закрывать файл после завершения работы с ним?
  5. Какие библиотеки необходимо подключить для работы с функцией fopen() и типом FILE?
  6. Как обрабатываются символы новой строки при использовании функции fscanf() для чтения данных из файла?
  7. Что делает функция fgets() и каков ее синтаксис?
  8. Чем отличаются функции getc(), fgetc() и fputc(), putc()?
  9. Как можно открыть файл в бинарном режиме?

Программа курса:

  1. Описание курса
  2. Введение в язык программирования C
  3. Типы данных в C и форматированный вывод
  4. Символьные типы и управляющие символы в C
  5. Операторы ветвления и switch в C
  6. Циклы и операторы в языке C
  7. Битовые операции в языке C
  8. Посимвольный ввод и вывод в C - буферизация
  9. Переменные, адреса и указатели в C
  10. Передача аргументов по ссылке и значению в C
  11. Форматированный ввод данных с использованием scanf
  12. Генерация псевдослучайных чисел на C
  13. Адресная арифметика в массивах C
  14. Передача массивов в функции и указатели
  15. Строки в языке C - особенности и функции работы
  16. Функции работы со строками в C
  17. Работа со структурами в C - создание и применение
  18. Динамические структуры данных в C
  19. Ввод и вывод данных из файлов в языке C
  20. Передача аргументов в C и работа с файлами
  21. Препроцессор в языке C - директивы и макросы
  22. Создание и компиляция многофайловых программ в C
  23. Использование статических и динамических библиотек в C