Файлы и потоки. Работа с файлами.
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;
}