В стандарте языка Си отсутствуют средства ввода-вывода. Все операции ввода-вывода реализуются с помощью функций, находящихся в библиотеке языка Си, поставляемой в составе конкретной системы программирования Си.
Особенностью языка Си, который впервые был применен при разработке операционной системы UNIX , является отсутст вие заранее спланированных структур файлов. Все файлы рассматриваются как неструктурированная последовательность байтов. При таком подходе к организации файлов удалось распространить понятие файла и на различные устройства. В UNIX конкретному устройству соответствует так называемый "специальный файл", а одни и те же функции библиотеки языка Си используются как для обмена данными с файлами, так и для обмена с устройствами.
Примечание. В UNIX .эти же функции используются также для обмена данными между процессами (выполняющимися программами).
Библиотека языка Си поддерживает три уровня ввода- вывода:
Последний уровень, обеспечивающий удобный специализированный обмен данными с дисплеем и портами ввода-вывода, мы рассматривать не будем в силу его системной зависимости. Например, он различен для MS - DOS , Windows и UNIX .
На уровне потокового ввода-вывода обмен данными производится побайтно. Такой ввод-вывод возможен как для собственно устройств побайтового обмена (печатающее устройство, дисплей), так и для файлов на диске, хотя устройства внешней амяти, строго говоря, являются устройствами поблочного обмена, т.е. за одно обращение к устройству производится считывание или запись фиксированной порции данных. Чаще всего минимальной порцией данных, участвующей в обмене с внешней памятью, являются блоки в 512 байт или 1024 байта. При вводе с диска (при чтении из файла) данные помещаются в буфер операционной системы, а затем побайтно или определенными порциями передаются программе пользователя. При выводе данных в файл они накапливаются в буфере, а при заполнении буфера записываются в виде единого блока на диск за одно обращение к последнему. Буферы операционной системы реализуются в виде участков основной памяти. Поэтому пересылки между буферами ввода-вывода и выполняемой прог- раммой происходят достаточно быстро в отличие от реальных обменов с физическими устройствами.
Функции библиотеки ввода-вывода языка Си, поддерживающие обмен данными с файлами на уровне потока, позволяют обрабатывать данные различных размеров и форматов, обеспечивая при этом буферизованный ввод и вывод. Таким образом,
Поток - это файл вместе с предоставляемыми средствами буферизации.
При работе с потоком можно производить следующие действия:
Для того чтобы можно было использовать функции библиотеки ввода-вывода языка Си, в программу необходимо включить заголовочный файл stdio.h (# include < stdio.h >), который содержит прототипы функций ввода-вывода, а также определения констант, типов и структур, необходимых для работы функций обмена с потоком.
На рис. 7.1 показаны возможные информационные обмены исполняемой программы на локальной (несетевой) ЭВМ.

Рис. 7.1, Информационные обмены исполняемой программы на локальной ЭВМ
Прежде чем начать работать с потоком, его необходимо инициализировать, т.е. открыть. При этом поток связывается в исполняемой программе со структурой предопределенного типа FILE . Определение структурного типа FILE находится в заголовочном файле stdio.h . Вот оно
typedef struct _iobuf
{
char* _ptr;
int _cnt;
char* _base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char* _tmpfname;
} FILE;
В структуре FILE содержатся компоненты, с помощью которых ведется работа с потоком, в частности: указатель на буфер, указатель (индикатор) текущей позиции в потоке и другая информация.
При открытии потока в программу возвращается указатель на поток, являющийся указателем на объект структурного типа FILE . Этот указатель идентифицирует поток во всех последующих операциях.
Указатель на поток, например fp , должен быть объявлен в программе следующим образом:
#include <stdio.h>
FILE *fp;
Указатель на поток приобретает значение в результате выполнения функции открытия потока:
fp = fopen (имя_файла,режим_открытия);
Параметры имя_файла и режим_открытия являются указателями на массивы символов, содержащих соответственно имя файла, связанного с потоком, и строку режимов открытия. Однако эти параметры могут задаваться и непосредственно в виде строк при вызове функции открытия файла. Например:
fp = fopen("t.txt", "r") ;
где t.txt — имя некоторого файла, связанного с потоком;
r - обозначение одного из режимов работы с файлом (тип доступа к потоку).
Стандартно файл, связанный с потоком, можно открыть в одном из следующих шести режимов:
" w " -новый текстовый (см. ниже) файл открывается для записи. Если файл уже существовал, то предыдущее содержимое стирается, файл создается заново;
"г" -существующий текстовый файл открывается только для чтения;
"а" -текстовый файл открывается (или создается, если файла нет) для добавления в него новой порции ин формации (добавление в конец файла). В отличие от режима " w " режим "а" позволяет открывать уже существующий файл, не уничтожая его предыдущей версии, и писать в продолжение файла;
" w +" - новый текстовый файл открывается для записи и последующих многократных исправлений. Если файл уже существует, то предыдущее содержимое стирается. Последующие после открытия файла запись и чтение из него допустимы в любом месте файла, в том числе запись разрешена и в конец файла, т.е. файл может увеличиваться ("расти");
"r+" - существующий текстовый файл открывается как для чтения, так и для записи в любом месте файла; однако в этом режиме невозможна запись в конец файла, т.е. недопустимо увеличение размеров файла;
"a+" - текстовый файл открывается или создается (если файла нет) и становится доступным для изменений, т.е. для записи и для чтения в любом месте; при этом в отличие от режима " w +" можно открыть существующий файл и не уничтожать его содержимого; в отличие от режима "r+" в режиме "а+" можно вести запись в конец файла, т.е. увеличивать его размеры.
Поток можно открыть в текстовом либо двоичном (бинарном) режиме.
В текстовом режиме прочитанная из потока комбинация символов CR (значение 13) и LF (значение 10), то есть управляющие коды "возврат каретки" и "перевод строки", преобразуется в один символ новой строки '\n' (значение 10, совпадающее с LF ). При записи в поток в текстовом режиме осуществляется обратное преобразование, т.е. символ новой строки '\ n ' ( LF ) заменяется последовательностью CR и LF .
Если файл, связанный с потоком, хранит не текстовую, а произвольную двоичную информацию, то указанные преобразования не нужны и могут быть даже вредными. Обмен без такого преобразования выполняется при выборе двоичного или бинарного режима, который обозначается буквой b. Например, " r + b " или " wb ". В некоторых компиляторах текстовый режим обмена обозначается буквой t , т.е. записывают " a + t " или " rt ".
Если поток открыт для изменений, т.е. в параметре режима, т.е. в параметре режима присутствует символ "+", то разрешены как вывод в поток, так и чтение из него. Однако смена режима (переход от записи к чтению и обратно) должна происходить только после установки указателя потока в нужную позицию.
При открытии потока могут возникнуть следующие ошибки:
Необходимо также отметить, что при выполнении функции fopen ( ) происходит выделение динамической памяти. При ее отсутствии устанавливается признак ошибки " Not enough memory " (недостаточно памяти). В перечисленных случаях указатель на поток приобретает значение NULL . Заметим, что указатель на поток в любом режиме, отличном от аварийного, никогда не бывает равным NULL .
Приведем типичную последовательность операторов, которая используется при открытии файла, связанного с потоком:
if (( fp = fopen("t.txt", "w"))= = NULL)
{ perror (" ошибка при открытии файла t.txt \n") ; exit(0) ;}
где NULL - нулевой указатель, определенный в файле stdio.h .
Для вывода на экран дисплея сообщения об ошибке при открытии потока используется стандартная библиотечная функция реггог( ), прототип которой в stdio.h имеет вид:
void perror (const char * s);
Функция реггог() выводит строку символов, адресуемую указателем s , за которой размещаются: двоеточие, пробел и сообщение об ошибке. Содержимое и формат сообщения
определяются реализацией системы программирования. Текст сообщения об ошибке выбирается функцией perror () на основании номера ошибки. Номер ошибки заносится в переменную interrno (определенную в заголовочном файле errno . h ) рядом функций библиотеки языка Си, в том числе и функциями ввода- вывода.
После того как файл открыт, с ним можно работать, записывая в него информацию или считывая ее (в зависимости от режима).
Открытые на диске файлы после окончания работы с ними рекомендуется закрыть явно. Для этого используется библиотечная функция
int fclose (указатель_на_поток );
Открытый файл можно открыть повторно (например, для изменения режима работы с ним) только после того, как файл будет закрыт с помощью функции fclose ( ).
Когда программа начинает выполняться, автоматически открываются пять потоков, из которых основными являются:
По умолчанию стандартному потоку ввода stdin ставится в соответствие клавиатура, а потокам stdout и stderr соответствует экран дисплея.
Для ввода-вывода данных с помощью стандартных потоков в библиотеке языка Си определены следующие функции:
Ввод-вывод отдельных символов. Одним из наиболее эф фективных способов осуществления ввода-вывода одного сим- вЗДа является использование библиотечных функций getchar () и putchar ( ). Прототипы функций getchar () и putchar ( ) имеют следующий вид:
int getchar ( void );
int putchar ( int с);
Аналогичным образом (так же как это делается при работе со стандартными потоками ввода-вывода stdin и stdout ) можно осуществлять работу с файлами на диске. Для этой цели в библиотеку языка Си включены следующие функции:
fgetc ( ), getc ( ) - ввод (чтение) одного символа из файла;
fputc (), putc ( ) - запись одного символа в файл;
fprintf ( ) - форматированный вывод в файл;
fscanf ( ) - форматированный ввод (чтение) из файла;
fgets ( ) - ввод (чтение) строки из файла;
fputs ( ) - запись строки в файл.
Различие между функциями fgetc (), getc () и fputc ( ), putc () здесь не рассматривается, и поэтому в примерах мы будем использовать только одну из них.
Двоичный (бинарный) режим обмена с файлами. Двоичный режим обмена организуется с помощью функций getc () и putc ( ), обращение к которым имеет следующий формат:
с = getc(fp); putc(c, fp);
где fp - указатель на поток;
с - переменная типа int для приема очередного символа из файла или для записи ее значения в файл.
Прототипы функции :
int getc (FILE *stream );
int putc (int c, FILE *stream );
В качестве примера использования функций getc () и putc () рассмотрим программы ввода данных в файл с клавиатуры и программу вывода их на экран дисплея из файла.
Программа ввода читает символы с клавиатуры и записывает их в файл. Пусть признаком завершения ввода служит поступивший от клавиатуры символ '#'. Имя файла запрашивается у пользователя. Если при вводе последовательности символов была нажата клавиша < Enter >, служащая разделителем строк при вводе с клавиатуры, то в файл записываются управляющие коды "Возврат каретки" ( CR - значение 13) и "Перевод строки" ( LF - значение 10). Код CR в дальнейшем при выводе вызывает перевод маркера (курсора) в начало строки экрана дисплея. Код LF служит для перевода маркера на новую строку дисплея. Значения этих кодов в тексте программы обозначены соответственно идентификаторами CR и LF , т.е. CR и LF - именованные константы. Запись управляющих кодов CR и LF в файл позволяет при последующем выводе файла на экран отделить строки друг от друга.
Ниже приводится пример функций
fwrite записи строки в файл, fgets чтения строки из файла, fseek установки указателя текущей позиции в потоке..
/* fgets example */
#include <string.h>
#include <stdio.h>
int main(void)
{
FILE *stream;
char string[] = "This is a test";
char msg[20];
/* open a file for update */
stream = fopen("DUMMY.txt", "w+");
/* write a string into the file */
fwrite(string, strlen(string), 1, stream);
/* seek to the start of the file */
fseek(stream, 0, SEEK_SET);
/* read a string from the file */
fgets(msg, strlen(string)+1, stream);
/* display the string */
printf("%s", msg);
fclose(stream);
return 0;
}
Интереснее пример с использованием функции даты-времени и использование операторов цикла для чтения из файла.
/* fgets, ctime example */
#include <string.h>
#include <stdio.h>
#include <time.h>
int main(void)
{ char a;
FILE *stream;
time_t t;
time(&t);
char string[50]="gggggggggggggggggggg \n";
char msg[50];
//string = ctime(&t);
printf("%s\n",ctime(&t ));
/* open a file for update
îòêðûòü ôàéë äëÿ äîáàâëåíèÿ*/
stream = fopen("DUMMY.txt", "a+");
/* write a string into the file */
fwrite(ctime(&t) , strlen(ctime(&t)), 1, stream);
/* seek to the start of the file
óñòàíîâèòü óêàçàòåëü òåêóùåé ïîçèöèè â ïîòîêå*/
fseek(stream, 0, SEEK_SET);
/* read a string from the file */
while (fgets(msg, 200, stream)!=NULL)
printf("%s", msg);
fseek(stream, 0, SEEK_SET);
while (fgets(msg, strlen(ctime(&t))+1, stream)!=NULL)
/* display the string */
printf("%s", msg);
fclose(stream);
scanf("%c",&a);
return 0;
}
Примеры выполнены в программной среде Dev-C++ (Free Software )
Выполнить программы записанные выше..
Выведите информацию о буфере на экран.