Лекция 10. Файловый ввод-вывод

Пример программы, копирующей файлы

Сразу начнём с примера. Данная программа копирует файл. При запуске она просит пользователя ввести имя файла-источника и открывает его. Затем она просит пользователя ввести имя файла-приёмника, считывает его и открывает файл с таким именем. Далее, пока во входном файле не кончились символы, она каждый прочитанный символ копирует в выходной файл.

#include <stdio.h>

int main(void)
{
    char source[80]; // Строка для имени копируемого файла.
    printf("Введите имя файла для копирования.\n");
    scanf("%s", source); // при считывании строки & не нужен
    FILE *in = fopen(source, "rb");
    // Переменная типа FILE* с именем in. В дальнейшем используется
    // для считывания символов из открытого файла.  Сам файл открыт
    // в режиме бинарного чтения (read binary).
    // Если fopen не сможет открыть файл, она вернёт константу NULL:
    if (NULL == in) {
    {
        printf("Невозможно читать файл \"%s\"\n", source);
        return 1;
    }
    char target[80]; // Имя файла, в который будем копировать
                     // символы.
    srintf("Введите имя файла, в который будут копироваться символы.\n");
    scanf("%s", target);
    FILE *out = fopen(target, "wb");
    if (NULL == out) {
    {
        printf("Невозможно записывать в файл \"%s\"\n", target);
        return 1;
    }
    int c;
    // ВНИМАНИЕ! Лишние скобки вокруг оператора = здесь крайне важны,
    // так как без них оператор != будет выполняться до присваивания,
    // и в переменной c будет лежать результат сравнения, а не значение,
    // прочитанное функцией fgetc
    while((c = fgetc(in)) != EOF) {
        fputc(c, out); // Пишем прочитанный символ в выходной файл.
    }
    fclose(in); // Закрываем файлы после работы.
    fclose(out);
    return 0;
}

Прототип функции - объявление её “типа” без тела функции.

Прототипы функций, переменных и констант для ввода-вывода находятся в заголовочном файле stdio.h:

#include <stdio.h>

Среди прочих, там есть следующие функции:

int getchar(void); // Прототип функции
// считывает один символ со стандартного потока ввода
// и возвращает его, как unsigned char, приведённый к int,
// если символа на входе нет, ожидает до тех пор, пока он
// не появится. Если поток ввода закрыт, вернёт EOF
// (константа End of File).

int putchar(int c);
// кладёт символ c в стандартный поток вывода. Возвращает
// сам c.

Потоки ввода-вывода и функция fopen

FILE * – поток ввода-вывода может быть подключён к файлу или терминалу.

Существует три переменных с потоками, определёнными по умолчанию (стандартные потоки ввода-вывода):

FILE *stdin; /// Стандартный поток ввода (обычно на терминал)
FILE *stdout; /// Стандартный поток вывода (обычно на терминал)
FILE *stderr; /// Стандартный поток вывода ошибок. (обычно на терминал)

Работа с файлами:

FILE *fopen(char name[], char mode[]);
// Возвращает указатель на файл, если не может открыть
// файл, возвращает NULL (нулевой указатель).

// name -- имя файла.
// mode -- режим работы:
//   "r" -- текстовое чтение
//   "w" -- текстовая запись
//   "rb" -- текстовое чтение
//   "wb" -- бинарная запись
//   "a" -- текстовая дозапись в конец файла
//   "ab" -- бинарная дозапись в конец файла

При открытии файла в режиме “w” или “wb”, если файл существует, его содержимое будет удалено, размер установлен в ноль и пойдёт запись с начала. Если файл не существует, он будет создан и открыт на запись.

Если fopen не смогла открыть файл, она вернёт NULL, если это не отследить и не принять мер, то при работе с NULL произойдёт ошибка типа Segmentation Fault.

В бинарном режиме работа все символы читаются и пишутся “как есть”, без преобразований.

В текстовом режиме читаемые и записываемые переводы строк переводятся в формат, принятый в данной ОС (“\n” в Unix, “\r\n” в Windows и MacOS).

Пример для Windows:

Режим “r”:

  • в файле “\n”, будет прочитано “\r\n”
  • в файле “\r\n”, будет прочитано “\r\n”

Режим “w”:

  • записываем “\n”, в файл записывается “\r\n”
  • записываем “\r\n”, в файл записывается “\r\n”
int fgetc(FILE *f);

// работает аналогично getchar, но для потока ввода f
// getchar()  <==>  fgetc(stdin);

int fputc(int c, FILE *stream);

// аналогично putchar, но для потока вывода f.
// putchar(c)  <==>  fputc(c, stdin);

int fclose(FILE *f);

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

Функции fprintf и fscanf

Для удобства работы с файлами, есть аналоги функций printf и scanf, работающие точно таким же образом, но принимающие дополнительный аргумент типа FILE * и использующие его вместо соответствующего стандартного потока.

int fprintf(FILE *f, char format[], ...);
// Работает так же как и printf, но направляет вывод
// в файл f. Три точки в языке C означают переменное
// число аргументов.

int fscanf(FILE *f, char format[], ...);
// Работает так же как и scanf, но читает данные из
// файла f. Возвращает число успешно прочитанных аргументов.
// Если произошла ошибка чтения (например, пользователь вынул
// флешку с файлом) или достигнут конец файла, вернёт EOF.

Пример:

// Пример fscanf. Прочитать из файла input.txt первое слово и вывести его.
FILE *in = fopen("input.txt", "rb");
char word[200];
fscanf(in, "%s", word);
printf("first word in file is %s\n", word);
close(in);


// Пример на fprintf. Выводит в файл hello.txt фразу "Hello, world!"
FILE *out = fopen("hello.txt", "wb");
fprintf(out, "Hello, world!");
fclose(out);

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

FILE *in = fopen("input.txt", "rb");
int i;
int arr[10];
// Прочитать 10 чисел из файла
for (i = 0; i < 10; ++i) {
    fscanf("%d", &a[i]);
}

Функции snprintf и sscanf

Две данные функции не относятся к работе с файлами, но так как мы уже обсудили fprintf и fscanf, давайте рассмотрим и похожие на них snprintf и sscanf. Эти функции предназначены для форматированной печати символов в строку и считывания по формату из строк, соответственно.

int snprintf(char out[], int out_size, char format[], ...);

// Работает так же, как и printf, результат записывается в
// массив out. Записывает в out не более out_size символов
// (чтобы не выйти за границу массива), на конце всегда
// будет '\0'.

Пример работы snprintf:

char str[20];
char dir[10] = "files";
int n = 23;
snprintf("str, sizeof(str), "%s/%d.txt", dir, n);
// Теперь в str лежит строка "dir/23.txt"

Функция sscanf:

int sscanf(char input[], char format[], ...);
// Аналогично scanf считывает аргументы по формату, указанному
// в строке format, но ввод считывается из строки input.

Пример:

char str[20] = "123 abc";
int x;
char str2[10];
sscanf(str, "%d%s", &a, str2);
// Теперь в a лежит число 123,
// а в str2 лежит строка "abc"