Interested Article - Файловый ввод-вывод в языке Си
- 2021-08-12
- 2
Язык программирования Си
поддерживает множество
функций
стандартных библиотек
для
файлового
ввода и вывода
. Эти функции составляют основу
заголовочного файла
стандартной библиотеки языка Си
<
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
или функции позиционирования в файле (
,
fsetpos
или
), а чтение не может следовать за записью без промежуточного вызова функции позиционирования в файле.
Режимы записи и добавления пытаются создать файл с заданным именем, если такого файла ещё не существует. Как указывалось выше, если эта операция оканчивается неудачей,
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
одинакового размера
[
каких?
]
[
источник не указан 4753 дня
]
, даже «правильный» вариант будет работать некорректно из-за сходства
EOF
и другого символа. Правильным вариантом обработки подобной ситуации является проверка
feof
и
после того, как
getchar
вернет
EOF
. Если
feof
определит, что конец файла ещё не достигнут, а
ferror
«сообщит», что ошибок нет, то
EOF
, возвращённый
getchar
, может считаться текущим символом. Такие дополнительные проверки делаются редко, так как большинство программистов считает, что их код никогда не будет выполняться на подобных системах с «большим
char
». Другой способ состоит в использовании проверки при компиляции, что
UINT_MAX > UCHAR_MAX
, которая хотя бы предотвратит компиляцию на подобных системах.
[
источник не указан 290 дней
]
при помощи 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;
}
См. также
Дополнительные источники
- (en)
-
-
-
-
-
в вопросах-ответах по Си: использование
char
для хранения возвращаемогоgetc
значения
- 2021-08-12
- 2