С++. Лекция 6. Файлы и потоки. Структуры и перечисления.

Файлы и потоки. Работа с файлами.

1.1. Файлы и потоки.

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

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

                В текстовом режиме поток записывает и читает из файла текстовые строки, которые заканчиваются символом ‘\n’ и могут содержать символ ‘\t’. По стандарту поток должен обеспечивать обработку строк длиной не менее 254 символа, включая символ ‘\n’. Стандартом допускается, что при чтении и записи данных текстовым потоком может происходить их преобразование.

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

                В следующих параграфах будут описаны стандартные функции для работы с файлами, используемые в языке С. Прототипы этих функций находятся в заголовочном файле stdio.h.

1.2. Соединение и отсоединение потока от файла. Перенаправление потока.

                Для соединения потока с файлом используется функция:

                FILE*  fopen(const  char*  filename, const  char*  mode);

которая открывает файл, имя которого задано параметром filename, в режиме, заданнном параметром mode. В случае успешного завершения функция fopen возвращает указатель на поток, а в случае неудачи – NULL.

                Параметр mode может принимать следующие значения:

                “r” – чтение в текстовом режиме,

                “w” – запись в текстовом режиме,

                “a” – присоединение в текстовом режиме,

                “rb” – чтение в бинарном режиме,

                “wb” – запись в бинарном режиме,

                “ab” – присоединение в бинарном режиме,

                “r+” или “w+” или “a+” – чтение и запись в текстовом режиме,

                “r+b” или “w+b” или “a+b” – чтение и запись в бинарном режиме,

                “rb+” или “wb+” или “ab+” – чтение и запись в бинарном режиме.

                При открытии файла в режимах “r”, “rb”, “r+”, “r+b” его индикатор позиции устанавливается на начало файла. В случае, если открывается несуществующий файл, то функция fopen заканчивается неудачей.

                При открытии файла в режимах “w”, “wb”, “w+”, “w+b” создается новый файл. Если файл с заданным именем существует, то его содержимое стирается, а индикатор позиции устанавливается на начало файла.

                При открытии файла в режимах “a”, “ab”, “a+”, “a+b” создается новый файл. Если файл с заданным именем существует, то он открывается, и индикатор позиции устанавливается на конец файла.

                Следует учитывать, что если текстовый файл открывается в режиме чтения и записи, то базовая операционная система может открыть его в бинарном режиме. Максимальное количество файлов, которые можно открыть одновременно задается переменной FOPEN_MAX (равно 20 в Visual C++). Максимальная длина имени файла задается переменной FILENAME_MAX (равно 260 в ОС Windows).

                Для отсоединения потока от файла используется функция:

                int  fclose(FILE*  stream);

которая закрывает файл, при этом освобождая все буферы потока. При успешном завершении функция возвращает 0, а в случае неудачи – EOF.

                Для перенаправления потока используется функция:

                FILE*  freopen(const  char*  filename, const  char*  mode, FILE*  stream);

которая закрывает файл, соединенный с потоком stream, и соединяет с этим потоком файл filename в режиме mode. В случае успеха функция возвращает указатель на поток, а в случае неудачи – NULL. Параметр mode принимает те же значения, что и в функции fopen.

                Поясним подробнее разницу между текстовым и бинарным режимами работы потока. Как в текстовом так и в бинарном режиме можно использовать все функции для доступа к файлу. При работе в бинарном режиме поток записывает на диск и считывает с диска точные копии данных, переданные функциями записи данных на диск и требуемые функциями чтения данных с диска соответственно. Работа потока в текстовом режиме отличается тремя моментами от работы потока в бинарном режиме. Во-первых, в этом случае символ CTRL+Z интерпретируется как конец файла. Во-вторых, при записи в текстовый поток из комбинации символов “\r” (carriage return, возврат каретки) и “\n” (new line, новая строка) в файл записывается только символ “\n”, а при чтении из текстового потока символ “\n” преобразуется в комбинацию символов “\r”+”\n”. В-третьих, так как при записи в текстовый поток может происходить преобразование количества и представления символов, то для получения требуемой позиции в файле нужно использовать только функции fgetpos и ftell.

1.3. Работа с индикаторами ошибки, позиции и конца файла.

                1. Работа с индикатором ошибки. С каждым потоком связан индикатор ошибки, который находится в установленном положении, если в потоке, связанном с файлом произошла ошибка. В противном случае индикатор ошибки находится в сброшенном состоянии. Для работы с индикатором ошибки используются функции ferror и clearerr.

                Функция

                int  ferror(FILE*  stream);

возвращает ненулевое значение, если индикатор ошибки установлен, в противном случае – возвращает 0.

                Функция

                void  clearerr(FILE*  strem);

сбрасывает индикаторы ошибки и конца файла для потока stream.

                2. Работа с индикатором конца файла. Структура FILE содержит индикатор конца файла, который устанавливается в ненулевое значение функцией чтения из файла при достижении этой функцией конца файла. Состояние конца файла читается функцией

                int  feof(FILE*  stream);

которая возвращает ненулевое значение, если индикатор конца файла установлен, в противном случае функция возвращает 0.

                3. Работа с индикатором позиции. Для каждого файла, после его открытия, определяется индикатор позиции, который указывает на смещение от начала файла в байтах. Тип индикатора позиции определяется как

                typedef  long  fpos_t;

                Для работы с индикатором позиции предназначены следующие функции. Функция

                void  rewind(FILE*  stream);

устанавливает индикатор позиции на начало файла, связанного с потоком stream. При этом сбрасывется индикатор ошибки и конца файла.

                Функция

                int  fseek(FILE*  stream,  long  offset, int  mode);

сдвигает индикатор позиции файла на offset байт. В случае успешного завершения функция возвращает 0, в противном случае – ненулевое значение. Параметр mode указывает на режим сдвига и может принимать следующие значения:

                SEEK_SET – смещение от начала файла,

                SEEK_CUR – смещение от текущей позиции,

                SEEK_END – смещение от конца файла.

При работе с текстовым потоком должны использоваться только следующие комбинации значений параметров:

                mode = SEEK_SET            offset = 0 или

                                                               offset = значению, возвращаемому функцией ftell

                mode = SEEK_CUR          offset = 0

                mode = SEEK_END           offset = 0

                Функция

                int  fsetpos(FILE*  stream, const  fops_t  *pos);

устанавливает индикатор позиции файла stream в позицию, на которую указывает параметр pos. Индикатор конца файла сбрасывается. В случае успеха функция возвращает 0, а в случае неудачи возвращает ненулевое значение и устанавливает переменную errno.

                Функция

                long  ftell(FILE*  stream);

в случае успешного завершения возвращает текущую позицию файла stream, а в случае неудачи – возвращает значение 1L и устанавливает значение переменной errno. Для бинарного потока позиция равна смещению в байтах от начала файла, а в случае текстового потока – значению, которое может использоваться функцией fseek.

                Функция

                int  fgetpos(FILE*  stream, fops_t*  pos);

записывает текущую позицию файла stream по адресу pos. В случае успеха функция возвращает 0, а в случае неудачи – ненулевое значение и устанавливает значение переменной errno.

1.4. Блочный ввод-вывод.

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

                Для записи блока в файл используется функция

                size_t  fwrite(const  void*  ptr, size_t  size, size_t  nitems, FILE*  stream);

которая записывает содержимое блока памяти, на который указывает ptr, в файл stream. Длина записываемого блока определяется как произведение size*nitems. Функция возвращает число записанных единиц памяти. В случае удачи это число должно быть равно nitems.

                Для чтения блока из файла используется функция

                size_t  fread(const  void*  ptr, size_t  size, size_t  nitems, FILE*  stream);

параметры которой имеют тот же смысл, что и в функции fwrite.

                По стандарту состояние индикатора позиции после работы функций fwrite и fread не определено.

                В следующей программе создается бинарный файл.

// создание бинарного файла

#include <stdio.h>

struct emp

{

                int code;

                char name[20];

                double salary;

};

int main()

{

                FILE* out;                           // выходной поток

                struct emp s;                        // для записей файла

                               // открываем выходной поток в бинарном режиме

                if(!(out = fopen("C:\\employee.bin", "wb")))

                {

                               printf("Open file failed.\n");

                               return 0;

                }

                printf("Input code, name and salary.\n");

                printf("Press Ctrl+z to exit.\n");

                printf(">");

                               // вводим первую запись с консоли

                scanf("%d%s%lf", &s.code, &s.name, &s.salary);

                while (!feof(stdin))

                {

                                               // пишем запись в файл

                               fwrite(&s, sizeof(struct emp), 1, out);

                               printf(">");

                                               // вводим следующие записи с консоли

                               scanf("%d%s%lf", &s.code, &s.name, &s.salary);

                }

                               // закрываем выходной поток

                fclose(out);

                return 1;

}

                В следующей программе выполняется чтение записей из бинарного файла.

// чтение бинарного файла

#include <stdio.h>

struct emp

{

                int code;

                char name[20];

                double salary;

};

int main()

{

                FILE* in;                              // выходной поток

                struct emp s;                        // для записей файла

                unsigned i;                            // номер записи

                               // открываем входной поток в бинарном режиме

                if(!(in = fopen("C:\\employee.bin", "rb")))

                {

                               printf("Open file failed.\n");

                               return 0;

                }

                printf("Press Ctrl+z to exit.\n");

                               // читаем индекс

                printf("Input an index: ");

                scanf("%u", &i);

                while (!feof(stdin))

                {

                                               // устанавливает указатель на нужную запись

                               fseek(in, i*sizeof(struct emp), SEEK_SET);

                                               // читаем запись из файла

                               if(!fread(&s, sizeof(struct emp), 1, in))

                               {

                                               printf("The wrong index.\n");

                                               continue;

                               }

                                               // выводим запись на консоль

                               printf("\tcode = %d name = %s sal = %f\n",

                                               s.code, s.name, s.salary);

                                               // читаем индекс

                               printf("Input an index: ");

                               scanf("%u", &i);

                }

                               // закрываем входной поток

                fclose(in);

                return 1;

}

1.5. Символьный ввод-вывод.

                Символьный ввод-вывод используется с текстовыми потоками.

                1. Ввод-вывод символов. Для записи и чтения символов из текстового файла используются функции fputc, putc, fgetc, getc.

                Функция

                int  fputc(int  c, FILE*  stream);

записывает символ с в поток stream и продвигает индикатор позиции на следующий символ. В случае успеха функция возвращает символ с, а в случае неудачи EOF и устанавливает индикатор ошибки.

                Функция

                int  putc(int  c, FILE*  stream);

работает так же как и функция fputc, но может быть реализована как макрокоманда.

                Функция

                int  fgetc(FILE*  stream);

читает символ из потока stream и продвигает индикатор позиции на следующий символ. В случае успешного завершения функция возвращает прочитанный символ. В случае достижения конца файла функция возвращает EOF и устанавливает индикатор конца файла. В случае ошибки функция возвращает EOF и устанавливает индикатор ошибки.

                Функция

                int  getc(FILE*  stream);

работает так же кака и функция fgetc, но может быть реализована как макрокоманда.

                Функция

                int  ungetc(int  c, FILE*  stream);

записывает символ с в поток stream. Функции fseek, fsetpos и rewind игнорируют такие символы. Доступ к записанным символам выполняется по правилу FIFO. В случае успеха функция возвращает записанный символ, а в случае неудачи – EOF.

                2. Ввод-вывод строк. Для записи и чтения строк из символьного потока используются символы fpus и fgets.

                Функция

                int  fputs(const  char*  str, FILE*  stream);

записывает строку str в файл stream, не включая завершающий нулевой байт. В случае упешного завершения функция возвращает ненулевое число, а в случае неудачи – EOF.

                Функция

                int  fgets(char*  str, int  n, FILE*  stream);

читает строку из потока stream в строку str. Останавливается функция в случае, если прочитан (n-1) символ, или встретился символ ‘\n’, или достигнут конец файла.. В любом из этих случаев в конец строки помещается символ ‘\n’. В случае успеха функция возвращает указатель str, а в случае неудачи – NULL. Строка str не изменяется, если не прочитан ни один символ и встретился конец файла.

                3. Форматированный ввод-вывод. Для форматированного ввода-вывода в текстовые файлы используются функции fscanf и fprintf.

                Функция

                int  fprintf(FILE*  stream, const  char*  format, …);

выполняет вывод в файл stream в соответствии с форматной строкой format. Работает эта функция так же, как и функция форматирования строк sprintf, которая была рассмотрена в лабораторной работе №5.

                Функция

                int  fscanf(FILE*  stream, const  char*  format, …);

выполняет ввод из файла stream текста в соответствии с форматной строкой format. Работает эта функция так же, как и функция форматирования строк sscanf, которая была рассмотрена в лабораторной работе №5.

                В следующей программе создается текстовый файл.

// создание текстового файла

#include <stdio.h>

int main()

{

                int code;

                char name[80];

                double salary;

                FILE* out;           // выходной поток

                               // открываем выходной поток в текстовом режиме

                if(!(out = fopen("C:\\employee.txt", "w")))

                {

                               printf("Open file failed.\n");

                               return 0;

                }

                printf("Input code, name and salary.\n");

                printf("Press Ctrl+z to exit.\n");

                printf(">");

                               // вводим первую запись с консоли

                scanf("%d%s%lf", &code, &name, &salary);

              while (!feof(stdin))

                {

                                               // пишем запись в файл

                               fprintf(out, "%d %s %f ", code, name, salary);

                               printf(">");

                                               // вводим следующие записи с консоли

                               scanf("%d%s%lf", &code, &name, &salary);

                }

                               // закрываем выходной поток

                fclose(out);

                return 1;

}

               В следующей программе читается текстовый файл.

// чтение текстового файла

#include <stdio.h>

int main()

{

                int code;

                char name[80];

                double salary;

                FILE* in;                              // входной поток

                               // открываем входной поток в текстовом режиме

                if(!(in = fopen("C:\\employee.txt", "r")))

                {

                               printf("Open file failed.\n");

                               return 0;

                }

                               // читаем первую запись

                fscanf(in, "%d%s%lf", &code, name, &salary);

                while (!feof(in))

                {

                                               // выводим запись на консоль

                               printf("code = %d name = %s sal = %f\n", code, name, salary);

                                               // читаем следующие записи

                               fscanf(in, "%d%s%lf", &code, name, &salary);

                }

                               // закрываем входной поток

                fclose(in);

                return 1;

}

1.6. Работа с буферами.

                Буфером называется область оперативной памяти, используемая потоком для временного хранения данных из файла. Для работы с буферами используются функции setvbuf, setbuf, fflush.

                Функция

                int  setvbuf(FILE*  stream, char*  buffer, int  mode, size_t  size);

определяет буфер ввода-вывода и режим работы с ним для потока stream. Вызывается эта функция после открытия файла, но перед доступом к нему. При успешном завершении функция возвращает значение 0, а вслучае неудачи – ненулевое значение.

                Параметр buffer указывает на блок памяти для буфера. Если этот параметр равен NULL, то функция setvbuf использует функцию malloc для захвата памяти под буфер.

                Параметр mode определяет режим работы с буфером и может принимать следующие значения:

                _IOFBF                 вывод данных из буфера во внешнюю память выполняется только при полной                                                       загрузке буфера или при закрытии файла;

                _IOLBF                вывод данных из буфера во внешнюю память выполняется при записи в буфер                                                      символа ‘\n’;

                _IONBF                нет буферизации, в этом случае параметры size и buffer игнорируются.

                Параметр size определяет длину буфера в байтах.

                Функция

                int  setbuf(FILE*  stream, char*  buffer);

вызывает функцию setvbuf. Причем, если значение параметра buffer не равно NULL, то функция setvbuf вызывается следующим образом:

                setvbuf(stream, buffer, _IOFBF, BUFSIZE);

где константа BUFSIZE задает длину буфера по умолчанию. Эта константа описана в заголовочном файле stdio.h. В противном случае функция setvbuf вызывается следующим образом:

                setvbuf(stream, 0, _IOFBF, BUFSIZE);

То есть в этом случае буферизация не используется.

                Функция

                int  fflush(FILE*  stream);

записывает данные из буфера потока stream, в соединенный с этим потоком файл. В случае успешного завершения функция возвращает значение 0, а в случае неудачи – EOF. Если значение параметра stream равно NULL, то освобождаются буферы всех потоков, которые работают в режиме вывода.

                В следующей программе создается текстовый файл с буфером.

// создание файла с буфером

#include <stdio.h>

int main()

{

                int code;

                char name[80];

                double salary;

                FILE* out;                                                                          // выходной поток

                const unsigned size = 1024;             // размер буфера

                char buffer[size];                                // буфер потока

                               // открываем выходной поток в текстовом режиме

                if(!(out = fopen("C:\\employee.txt", "w")))

                {

                               printf("Open file failed.\n");

                               return 0;

                }

                               // устанавливаем буфер для потока

                if(setvbuf(out, buffer, _IOFBF, size))

                {

                               printf("Set buffer failed.\n");

                               return 0;

                }

                printf("Input code, name and salary.\n");

                printf("Press Ctrl+z to exit.\n");

                printf(">");

                               // вводим первую запись с консоли

                scanf("%d%s%lf", &code, &name, &salary);

                while (!feof(stdin))

                {

                                               // пишем запись в файл

                               fprintf(out, "%d %s %f ", code, name, salary);

                               printf(">");

                                               // вводим следующие записи с консоли

                               scanf("%d%s%lf", &code, &name, &salary);

                }

                               // закрываем выходной поток

                fclose(out);

                return 1;

}

                В следующей программе создается текстовый файл без буфера.

// создание файла без буфера

#include <stdio.h>

int main()

{

                int code;

                char name[80];

                double salary;

                FILE* out;                                                                          // выходной поток

                               // открываем выходной поток в текстовом режиме

                if(!(out = fopen("C:\\employee.txt", "w")))

                {

                               printf("Open file failed.\n");

                               return 0;

                }

                               // нет буферизации

                setbuf(out, NULL);

                printf("Input code, name and salary.\n");

                printf("Press Ctrl+z to exit.\n");

                printf(">");

                               // вводим первую запись с консоли

                scanf("%d%s%lf", &code, &name, &salary);

                while (!feof(stdin))

                {

                                               // пишем запись в файл

                               fprintf(out, "%d %s %f ", code, name, salary);

                               printf(">");

                                               // вводим следующие записи с консоли

                               scanf("%d%s%lf", &code, &name, &salary);

                }

                               // закрываем выходной поток

                fclose(out);

                return 1;

}

1.8. Стандартные потоки.

                Каждой программе предоставляются три стандартных потока, которые по умолчанию соединены с консолью. Указатели на эти потоки возвращают макрокоманды stdin, stdout, stderr. Для работы со стандартными потоками предназначены рассмотренные ранее функции scanf, printf, а также следующие функции:

                int  putchar(int  c);                             // вывод символа в stdout

                int  getchar(void);                               // ввод символа из stdin

                int  puts(const  char*  str);                // вывод строки в stdout

                char*  gets(char*  str);                       // ввод строки из stdin

                void  perror(const  char*  str);          // вывод сообщения об ошибке в stderr

                Работают эти функции так же, как и аналогичные функции для работы с файлами.

1.9. Служебные функции для работы с файлами.

                В этом параграфе перечислим служебные функции для работы с файлами, которые не входят ни в одну из вышеперечисленных категорий. К ним относятся функции remove, rename, tmpfile, tmpname.

                Функция

                int  remove(const  char*  filename);

удаляет файл с именем filename. Если файл открыт, то работа функции зависит от реализации. В случае успешного завершения функция возвращает 0, а в случае неудачи – ненулевое значение.

                В следующей программе показывается, как удалить файл.

// удаление файла

#include <stdio.h>

int main()

{

                if(remove("C:\\employee.bin"))

                {

                               printf("There is no such a file.\n");

                               return 0;

                }

                printf("The file was deleted.\n");

                return 1;

}

                Функция

                int  rename(const  char*  old_filename, const  char*  new_filename);

переименовывает файл с именем old_filename в файл с именем new_filename. Если файл с именем new_filename уже существует, то работа функции зависит от реализации. В случае успешного завершения функция возвращает 0, а в случае неудачи – ненулевое значение.

                В следующей программе показывается, как переименовать файл.

// переименование файла

#include <stdio.h>

int main()

{

                if(rename("C:\\employee.txt", "C:\\emp.txt"))

                {

                               printf("There is no such a file.\n");

                               return 0;

                }

                printf("The file was renamed.\n");

               return 1;

}

                Функция

                FILE*  tmpfile(void);

создает временный файл в режиме “w+b”. После закрытия потока файл удаляется. В случае успешного завершения функция возвращает указатель на файл, а в случае неудачи – NULL.

                Функция

                char*  tmpnam(char*  str);

возвращает имя для временного файла. Максимальное количество имен равно TMP_MAX, а максимальная длина имени равна L_tmpnam. Если значение параметра str равно NULL, то функция возвращает указатель на свою строку, в противном случае возвращается указатель str.

                Следующая программа показывает пример использования временного файла.

// использование временного файла

#include <stdio.h>

int main()

{

                int code;

                char name[80];

                double salary;

                FILE* temp;                        // временный файл

                               // открываем временный файл

                if(!(temp = tmpfile()))

                {

                               printf("Create temp file failed.\n");

                               return 0;

                }

                printf("Input code, name and salary.\n");

                printf("Press Ctrl+z to exit.\n");

               printf(">");

                               // вводим первую запись с консоли

                scanf("%d%s%lf", &code, &name, &salary);

                while (!feof(stdin))

                {

                                               // пишем запись в файл во временный файл

                               fprintf(temp, "%d %s %f ", code, name, salary);

                               printf(">");

                                               // вводим следующие записи с консоли

                               scanf("%d%s%lf", &code, &name, &salary);

                }

                               // устанавливаем индикатор позиции на начало файла

                rewind(temp);

                printf("\nRead records from the temporary file.\n");

                               // читаем первую запись из временного файла

                fscanf(temp, "%d%s%lf", &code, name, &salary);

                while (!feof(temp))

                {

                                               // выводим запись на консоль

                               printf("code = %d name = %s sal = %f\n", code, name, salary);

                                               // читаем следующие записи из временного файла

                               fscanf(temp, "%d%s%lf", &code, name, &salary);

                }

                               // закрываем временный файл

                fclose(temp);

                return 1;

}