Interested Article - Файловый ввод-вывод в языке Си

Язык программирования Си поддерживает множество функций стандартных библиотек для файлового ввода и вывода . Эти функции составляют основу заголовочного файла стандартной библиотеки языка Си < 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)
  • — страница справки man по библиотечным функциям GNU/Linux (англ.)
  • — страница справки man по библиотечным функциям GNU/Linux (англ.)
  • — страница справки man по библиотечным функциям GNU/Linux (англ.)
  • — страница справки man по библиотечным функциям GNU/Linux (англ.)
  • в вопросах-ответах по Си: использование char для хранения возвращаемого getc значения
Источник —

Same as Файловый ввод-вывод в языке Си