- Файловый ввод/вывод в языке Си
-
Язык программирования Си поддерживает множество функций стандартных библиотек для файлового ввода и вывода. Эти функции составляют основу заголовочного файла стандартной библиотеки языка Си
<потоками байтов, которые могут быть как "потоками ввода", так и "потоками вывода". В отличие от некоторых ранних языков программирования, язык Си не имеет прямой поддержки произвольного доступа к файлам данных; чтобы считать записанную информацию в середине файла, программисту приходится создавать поток, ищущий в середине файла, а затем последовательно считывать байты из потока.
Потоковая модель файлового ввода-вывода была популяризирована во многом благодаря операционной системе Unix, написанной на языке Си. Большая функциональность современных операционных систем унаследовала потоки от Unix, а многие языки семейства языков программирования Си унаследовали интерфейс файлового ввода-вывода языка Си с небольшими отличиями (например, C++ отражает потоковую концепцию в своем синтаксисе (смотри
Содержание
Открытие файла при помощи fopen
Файл открывается при помощи
fopen
, которая возвращает информацию потока ввода-вывода, прикрепленного к указанному файлу или другому устройству, с которого идет чтение (или в который идет запись). В случае неудачи функция возвращает нулевой указатель.Схожая функция
freopen
библиотеки Си выполняет аналогичную операцию после первого закрытия любого открытого потока, связанного с ее параметрами.Они определяются как
FILE *fopen(const char *path, const char *mode); FILE *freopen(const char *path, const char *mode, FILE *fp);
Функция
fopen
по сути представляет из себя "обертку" более высокого уровня системного вызоваopen
операционной системы Unix. Аналогично,fclose
является оберткой системного вызова Unixclose
, а сама структураFILE
языка Си зачастую обращается к соответстующему файловому дескриптору Unix. В fdopen может использоваться для инициализации структурыFILE
файловым дескриптором. Тем не менее, файловые дескрипторы как исключительно Unix-концепция не представлены в стандарте языка Си.Параметр
mode
(режим) дляfopen
иfreopen
должен быть строковый и начинаться с одной из следующих последовательностей:режим описание начинает с.. r rb открывает для чтения начала w wb открывает для записи (создает файл в случае его отсутствия). Удаляет содержимое и перезаписывает файл. начала a ab открывает для добавления (создает файл в случае его отсутствия) конца r+ rb+ r+b открывает для чтения и записи начала w+ wb+ w+b открывает для чтения и записи. Удаляет содержимое и презаписывает файл. начала a+ ab+ a+b открывает для чтения и записи (добавляет в случае существования файла) конца Значение "b" зарезервировано для двоичного режима С. Стандарт языка Си описывает два вида файлов — текстовые и двоичные — хотя операционная система не требует их различать. Текстовый файл - файл, содержащий текст, разбитый на строки при помощи некоторого разделяющего символа окончания строки или последовательности (в Unix - одиночный символ перевода строки; в Microsoft Windows за символом перевода строки следует знак возврата каретки). При считывании байтов из текстового файла, символы конца строки обычно связываются (заменяются) с переводом строки для упрощения обработки. При записи текстового файла одиночный символ перевода строки перед записью связывается (заменяется) с специфичной для ОС последовательностью символов конца строки. Двоичный файл - файл, из которого байты считываются и выводятся в "сыром" виде без какого-либо связывания (подстановки).
При открытом файле в режиме обновления ( '+' в качестве второго или третьего символа аргумента обозначения режима) и ввод и вывод могут выполняться в одном потоке. Тем не менее, запись не может следовать за чтением без промежуточного вызова
fflush
или функции позиционирования в файле (fseek
,fsetpos
илиrewind
), а чтение не может следовать за записью без промежуточного вызова функции позиционирования в файле. [1]Режимы записи и добавления пытаются создать файл с заданным именем, если такого файла еще не существует. Как указывалось выше, если эта операция оканчивается неудачей,
fopen
возвращаетЗакрытие потока при помощи fclose
Функция
fclose
принимает один аргумент: указатель на структуруFILE
потока для закрытия.int fclose(FILE *fp);
Функция возвращает нуль в случае успеха и
Чтение из потока при помощи fgetc
Функция
fgetc
применяется для чтения символа из потока.int fgetc(FILE *fp);
В случае успеха,
fgetc
возвращает следующий байт или символ из потока (зависит от того, файл "двоичный" или "текстовый" (как выше обсуждалось). В противном случае,fgetc
возвращаетferror
илиfeof
с указателем на файл.)Стандартный макрос
getc
также определен в<stdio.h>
, успешно работая какfgetc
, кроме одного: будучи макросом, он может обрабатывать свои аргументы более одного раза.Стандартная функция
getchar
также определена в<stdio.h>
, она не принимает аргументов, и эквивалентнаgetc(stdin)
."Ловушка" EOF
Распространенной ошибкой является использование
fgetc
,getc
илиgetchar
для присваивания результата переменной типаchar
перед сравнением его сEOF
. Следующий фрагмент кода демонстрирует эту ошибку, а рядом приведен корректный вариант:Ошибка Правильно char c; while ((c = getchar()) != EOF) { putchar(c); }
int c; while ((c = getchar()) != EOF) { putchar(c); }
Нужно учитывать систему, в которой тип
char
, длина которого составляет 8 бит, представляет 256 различных значений.getchar
может возвращать любой из 256 возможных символов, а также может возвращатьEOF
для обозначения конца файла, т.е. всего 257 возможных возвращаемых значений.Когда результат
getchar
присваивается переменной типаchar
, которая может прдставить лишь 257 различных значений, присходит вынужденная потеря информации - при сжатии 257 значений в 256 "мест" проиходит коллизия. ЗначениеEOF
при конвертации вchar
становится неотличимым от любого из остальных 256 символов. Если этот символ обнаружен в файле, код, приведенный выше, может принять его за признак конца файла, или, что еще хуже, если типchar
- беззнаковый, тогда с учетом того, чтоEOF
- значение отрицательное, оно никогда не сможет стать равным любому беззнаковомуchar
, и таким образом, пример выше не закончится на метке конца файла, а будет выполняться вечно, повторно печатая символ, получающийся при конвертацииEOF
вchar
.В систамех, где
int
иchar
одинакового размера, даже "правильный" вариант будет работать некорректно из-за сходстваEOF
и другого символа. Правильным вариантом обработки подобной ситуации является проверкаferror
после того, какgetchar
вернетEOF
. Еслиfeof
определит, что конец файла еще не достигнут, аferror
"сообщит", что ошибок нет, тоEOF
, возвращенныйgetchar
может считаться текущим символом. Такие дополнительные проверки делаются редко, так как большинство программистов считает, что их код никогда не будет выполняться на подобных системах с "большимchar
". Другой способ состоит в использовании проверки при компиляции, чтоUINT_MAX > UCHAR_MAX
, которая хотя бы предотвратит компиляцию на подобных системах.fwrite
В языке программирование Си функции
fread
иfwrite
соответственно реализуют файловые операции ввода и вывода.fread
иfwrite
объявлены в<
Запись в файл при помощи fwrite
fwrite определяется как
int fwrite ( const void * array, size_t size, size_t count, FILE * stream );
Функция
fwrite
записывает блок данных в поток. Таким образом запишется массив элементовcount
в текущую позицию в потоке. Для каждого элемента запишетсяsize
байт. Индикатор позиции в потоке изменится на число байт, записанных успешно. Возвращаемое значение будет равноcount
в случае успешного завершения записи. В случае ошибки возвращаемое значение будет меньшеcount
.Следующая программа открывает файл пример.txt, записывает в него строку символов, а затем его закрывает.
#include <stdio.h> #include <string.h> #include <stdlib.h> int main(void) { FILE *fp; size_t count; char const *str = "привет\n"; fp = fopen("пример.txt", "wb"); if(fp == NULL) { perror("ошибка открытия пример.txt"); return EXIT_FAILURE; } count = fwrite(str, 1, strlen(str), fp); printf("Записано %lu байт. fclose(fp) %s.\n", (unsigned long)count, fclose(fp) == 0 ? "успешно" : "с ошибкой"); return 0; }
Запись в поток при помощи fputc
Функция
fputc
применяется для записи символа в поток.int fputc(int c, FILE *fp);
Параметр
c
"тихо" конвертируется вunsigned char
перед выводом. Если прошло успешно, тоfputc
возвращает записанный символ. Если ошибка, тоfputc
возвращаетEOF
.Стандартный макрос
putc
также определен в<stdio.h>
, работая в общем случае аналогичноfputc
, за исключением того момента, что будучи макросом, он может обрабатывать свои аргументы более одного раза.Стандартная функция
putchar
, также определенная в<stdio.h>
, принимает только первый аргумент, и является эквивалентнойputc(c, stdout)
, гдеc
является упомянутым аргументом.Пример использования
Нижеследующая программа на языке Си открывает двоичный файл с названием мойфайл, читает пять байт из него, а затем закрывает файл.
#include <stdio.h> #include <stdlib.h> int main(void) { char buffer[5] = {0}; /* инициализируем нулями */ int i, rc; FILE *fp = fopen("мойфайл", "rb"); if (fp == NULL) { perror("Ошибка при открытии \"мойфайл\""); return EXIT_FAILURE; } for (i = 0; (rc = getc(fp)) != EOF && i < 5; buffer[i++] = rc) ; fclose(fp); if (i == 5) { puts("Прочитанные байты..."); printf("%x %x %x %x %x\n", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4]); } else fputs("Ошибка чтения файла.\n", stderr); return EXIT_SUCCESS; }
Смотри также
Дополнительные источники
- Linux – страница помощи man в
- Linux – страница помощи man в
- Linux – страница помощи man в
- Linux – страница помощи man в
- Вопрос 12.1 в вопросах-ответах по Си: использование
char
для хранения возвращаемогоgetc
значения
Wikimedia Foundation. 2010.