Ввод данных из файла и вывод в файл

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

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

В языке программирования C указатель на файл имеет тип FILE и его объявление выглядит так:

FILE *myfile;

С другой стороны, функция fopen() открывает файл по указанному в качестве первого аргумента адресу в режиме чтения ("r"), записи ("w") или добавления ("a") и возвращает в программу указатель на него. Поэтому процесс открытия файла и подключения его к программе выглядит примерно так:

myfile = fopen("hello.txt", "r");

Примечание. В случае использования относительной адресации текущим/рабочим каталогом в момент исполнения программы должен быть тот, относительно которого указанный относительный адрес корректен. Место нахождения самого исполняемого файла не важно.

При чтении или записи данных в файл обращение к нему осуществляется посредством файлового указателя (в данном случае, myfile).

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

Объявление функции fopen() содержится в заголовочном файле stdio.h, поэтому требуется его подключение. Также в stdio.h объявлен тип-структура 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>
 
int 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++;
    }
}

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

fgets()

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

fgets(массив_символов, 
      количество_считываемых_символов, 
      указатель_на_файл)

Например:

fgets(str, 50, myfile)

Такой вызов функции прочитает из файла, связанного с указателем myfile, одну строку текста полностью, если ее длина меньше 50 символов с учетом символа '\n', который функция также сохранит в массиве. Последним (50-ым) элементом массива str будет символ '\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() (работает и то и другое) позволяет получить из файла очередной один символ.

#include <stdio.h>
 
#define N 80
 
int main () { 
    FILE *file;
    char arr[N];
    int i;
 
    file = fopen("fscanf.txt", "r");
 
    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);
 
    fclose(file);
}

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

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

Также как и ввод, вывод в файл может быть различным.

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

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

#include <stdio.h>
 
int main () { 
    FILE *file;
    struct food {
        char name[20]; 
        unsigned qty; 
        float price; 
    };
    struct food shop[10];
    char i=0;
 
    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++;
    }
    fclose(file);
}

Построчный вывод в файл (fputs(), в отличие от puts() сама не помещает в конце строки '\n'):

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

Пример посимвольного вывода:

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

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

С файлом можно работать не как с последовательностью символов, а как с последовательностью байтов. В принципе, с нетекстовыми файлами работать по-другому не возможно. Однако так можно читать и писать и в текстовые файлы. Преимущество такого способа доступа к файлу заключается в скорости чтения-записи: за одно обращение можно считать/записать существенный блок информации.

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

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

Функции fread() и fwrite() принимают в качестве параметров:

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

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

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

#include <stdio.h>
#include <string.h>
 
int 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 сохраняется количество реально считанных символов. Значение n может быть равно 50 или меньше. Данные помещаются в строку. То же самое происходит со вторым файлом. Далее первая строка присоединяется ко второй, и данные сбрасываются в третий файл.

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

Курс с решением части задач:
android-приложение, pdf-версия

Основы языка C