Файловый ввод-вывод в языке Си
Язык программирования Си поддерживает множество функций стандартных библиотек для файлового ввода и вывода. Эти функции составляют основу заголовочного файла стандартной библиотеки языка Си <stdio.h>
.
Функциональность ввода-вывода языка Си по текущим стандартам реализуется на низком уровне. Язык Си абстрагирует все файловые операции, превращая их в операции с потоками байтов, которые могут быть как «потоками ввода», так и «потоками вывода». В отличие от некоторых ранних языков программирования, язык Си не имеет прямой поддержки произвольного доступа к файлам данных; чтобы считать записанную информацию в середине файла, программисту приходится создавать поток, ищущий в середине файла, а затем последовательно считывать байты из потока.
Потоковая модель файлового ввода-вывода была популяризирована во многом благодаря операционной системе Unix, написанной на языке Си. Большая функциональность современных операционных систем унаследовала потоки от Unix, а многие языки семейства языков программирования Си унаследовали интерфейс файлового ввода-вывода языка Си с небольшими отличиями (например, PHP). Стандартная библиотека C++ отражает потоковую концепцию в своём синтаксисе (смотрите iostream).
Открытие файла при помощи функции 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
является оберткой системного вызова Unix close
, а сама структура FILE
языка Си зачастую обращается к соответствующему файловому дескриптору Unix. В POSIX-окружении функция 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» зарезервировано для двоичного режима С. Стандарт языка Си описывает два вида файлов — текстовые и двоичные — хотя операционная система не требует их различать (однако, для некоторых компиляторов, например LCC, указание 'b' при работе с бинарным файлом принципиально важно!). Текстовый файл — файл, содержащий текст, разбитый на строки при помощи некоторого разделяющего символа окончания строки или последовательности (в Unix — одиночный символ перевода строки\n
; в Microsoft Windows за символом перевода строки следует знак возврата каретки)\r\n
. При считывании байтов из текстового файла, символы конца строки обычно связываются (заменяются) с переводом строки для упрощения обработки. При записи текстового файла одиночный символ перевода строки перед записью связывается (заменяется) с специфичной для ОС последовательностью символов конца строки. Двоичный файл — файл, из которого байты считываются и выводятся в «сыром» виде без какого-либо связывания (подстановки).
При открытом файле в режиме обновления ('+' в качестве второго или третьего символа аргумента обозначения режима) и ввод и вывод могут выполняться в одном потоке. Тем не менее, запись не может следовать за чтением без промежуточного вызова fflush
или функции позиционирования в файле (fseek
, fsetpos
или rewind
), а чтение не может следовать за записью без промежуточного вызова функции позиционирования в файле. [1]
Режимы записи и добавления пытаются создать файл с заданным именем, если такого файла ещё не существует. Как указывалось выше, если эта операция оканчивается неудачей, fopen
возвращает NULL
.
Закрытие потока при помощи fcloseПравить
Функция fclose
принимает один аргумент: указатель на структуру FILE
потока для закрытия.
int fclose(FILE *fp);
Функция возвращает нуль в случае успеха и EOF в случае неудачи. При нормальном завершении программы функция вызывается автоматически для каждого открытого файла.
Чтение из потокаПравить
при помощи fgetcПравить
Функция fgetc
применяется для чтения символа из потока.
int fgetc(FILE *fp);
В случае успеха fgetc
возвращает следующий байт или символ из потока (зависит от того, файл «двоичный» или «текстовый», как выше обсуждалось). В противном случае fgetc
возвращает EOF
. (Отдельный тип ошибок можно определить вызовом 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 бит (в частности, архитектура x86), представляет 256 различных значений. getchar
может возвращать любой из 256 возможных символов, а также может возвращать EOF
для обозначения конца файла, значение которого не может совпадать ни с одним из значений char
.
Когда результат getchar
присваивается переменной типа char
, которая может представить лишь 256 различных значений, происходит вынужденная потеря информации — при сжатии 257 значений в 256 «мест» происходит коллизия. Значение EOF
при конвертации в char
становится неотличимым от любого из остальных 256 символов. Если этот символ обнаружен в файле, код, приведённый выше, может принять его за признак конца файла, или, что ещё хуже, если тип char
— беззнаковый, тогда с учётом того, что EOF
— значение отрицательное, оно никогда не сможет стать равным любому беззнаковому char
, и таким образом, пример выше не закончится на метке конца файла, а будет выполняться вечно, повторно печатая символ, получающийся при конвертации EOF
в char
.
В системах, где int
и char
одинакового размера[каких?][источник не указан 4566 дней], даже «правильный» вариант будет работать некорректно из-за сходства EOF
и другого символа. Правильным вариантом обработки подобной ситуации является проверка feof
и ferror
после того, как getchar
вернет EOF
. Если feof
определит, что конец файла ещё не достигнут, а ferror
«сообщит», что ошибок нет, то EOF
, возвращённый getchar
, может считаться текущим символом. Такие дополнительные проверки делаются редко, так как большинство программистов считает, что их код никогда не будет выполняться на подобных системах с «большим char
». Другой способ состоит в использовании проверки при компиляции, что UINT_MAX > UCHAR_MAX
, которая хотя бы предотвратит компиляцию на подобных системах.[источник не указан 103 дня]
при помощи fgetsПравить
Функция fgets
применяется для чтения строки из потока. Считывание происходит до тех пор пока не будет достигнут конец строки (hex:0D0A, эквивалентны в листингах \n) или длина строки, в которую происходит считывание.
Предположим, у нас есть файл some_file.txt с текстом
палиндромы А в Енисее - синева. А лама мала. А лис, он умен - крыса сыр к нему носила. (И. Бабицкий)
#include <stdio.h>
#include <string.h>
int main (int argc, char* argv[]) /* argc хранит количество параметров, а argv[] указатели на эти параметры.
Например, если мы запустим исполняемый файл "fgets_example param1 param2", то argc будет равно 3, а argv[] = {"fgets_example", "param1", "param2"}*/
{
FILE *file;
char *fname = "some_file.txt";
char result_string[20]; //Строка в 20 символов
file = fopen(fname,"r");
if(file == NULL)
{
printf("не могу открыть файл '%s'",fname);
return 0;
}
int i=0;
char *real_tail;
while(fgets(result_string,sizeof(result_string),file))
{
real_tail="";
printf("Строка %d:Длина строки - %d:",i++,strlen(result_string));
if(result_string[strlen(result_string)-1] == '\n')//проверяем является ли последний элемент в строке символом её окончания
{
real_tail="\\n";
result_string[strlen(result_string)-1]='\0';
};// эта часть кода добавлена лишь для отображения символа конца строки в консоль без перевода на новую строку
printf("%s%s\n",result_string,real_tail);
}
fclose(file);
return 0;
}
в результате выполнения мы получим
Строка 0:Длина строки - 11:палиндромы\n Строка 1:Длина строки - 19: А в Енисее - си Строка 2:Длина строки - 6:нева.\n Строка 3:Длина строки - 17: А лама мала.\n Строка 4:Длина строки - 19: А лис, он умен Строка 5:Длина строки - 19:- крыса сыр к нему Строка 6:Длина строки - 19:носила. (И. Бабицки Строка 7:Длина строки - 2:й)
Функция strlen определяет длину строки по количеству символов до '\0', например:
printf("%d",strlen("123 \0 123")); //выведет 4
fwriteПравить
В языке программирования Си функции fread
и fwrite
соответственно реализуют файловые операции ввода и вывода. fread
и fwrite
объявлены в <stdio.h>
.
Запись в файл при помощи fwriteПравить
fwrite определяется как
int fwrite ( const char * array, size_t size, size_t count, FILE * stream );
Функция fwrite
записывает блок данных в поток. Таким образом запишется массив элементов array
в текущую позицию в потоке. Для каждого элемента запишется 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, sizeof(char), strlen(str), fp);
printf("Записано %lu байт. fclose(fp) %s.\n", (unsigned long)count, fclose(fp) == 0 ? "успешно" : "с ошибкой");
fclose(fp);
return 0;
}
Запись в поток при помощи fputсПравить
Функция 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;
}
См. такжеПравить
Дополнительные источникиПравить
- stdio.h on Coding Programmer Page / C Library Reference and Examples (en)
fclose(3)
— страница справки man по библиотечным функциям GNU/Linux (англ.)fgetc(3)
— страница справки man по библиотечным функциям GNU/Linux (англ.)fopen(3)
— страница справки man по библиотечным функциям GNU/Linux (англ.)fputc(3)
— страница справки man по библиотечным функциям GNU/Linux (англ.)- Вопрос 12.1 в вопросах-ответах по Си: использование
char
для хранения возвращаемогоgetc
значения
Для улучшения этой статьи желательно:
|