Практикум на ЭВМ. Задание 2

В стандарте языка Си отсутствуют средства ввода-вывода. Все операции ввода-вывода реализуются с помощью функций, находящихся в библиотеке языка Си, поставляемой в составе конкретной системы программирования Си.

Особенностью языка Си, который впервые был применен при разработке операционной системы UNIX , является отсутст вие заранее спланированных структур файлов. Все файлы рассматриваются как неструктурированная последовательность байтов. При таком подходе к организации файлов удалось распространить понятие файла и на различные устройства. В UNIX конкретному устройству соответствует так называемый "специальный файл", а одни и те же функции библиотеки языка Си используются как для обмена данными с файлами, так и для обмена с устройствами.

Примечание. В UNIX .эти же функции используются также для обмена данными между процессами (выполняющимися программами).

Библиотека языка Си поддерживает три уровня ввода- вывода:

  1. потоковый ввод-вывод,
  2. ввод-вывод нижнего уровня,
  3. ввод-вывод для консоли и портов.

Последний уровень, обеспечивающий удобный специализированный обмен данными с дисплеем и портами ввода-вывода, мы рассматривать не будем в силу его системной зависимости. Например, он различен для 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 в файл позволяет при последующем выводе файла на экран отделить строки друг от друга.

Ниже приводится пример функций


/* 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 )

Задание 2

Выполнить программы записанные выше..

Выведите информацию о буфере на экран.