Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Lek13_14.doc
Скачиваний:
9
Добавлен:
13.07.2019
Размер:
695.3 Кб
Скачать

Переадресация ввода-вывода

Современные операционные системы рассматривают клавиатуру и экран компьютера как файловые устройства. Это имеет смысл, поскольку система считывает данные с клавиатуры точно так же, как из файла на диске. Аналогично, вывод данных на экран реализуется подобно записи в файл.

Предположим, что ваше приложение считывает данные с клавиатуры и выводит их на экран. И вот, для автоматизации работы вы решили поместить входные данные в файл SAMPLE.DAT и организовать чтение этого файла программой. С этой целью необходимо дать системе указание вместо клавиатуры, которая рассматривается как файл, использовать другой файл — SAMPLE.DAT. Подобный процесс называется переадресацией.

Например, в командной строке MS-DOS переадресация выполняется очень просто. Вы используете оператор < для переадресации ввода и > для переадресации вывода. Допустим, исполняемый файл вашего приложения называется REDIRECT. Тогда следующая команда сначала запустит программу REDIRECT, а затем инициирует ввод данных из файла SAMPLE.DAT вместо стандартного ввода с клавиатуры:

redirect< sample.dat

Следующая строка одновременно осуществит переадресацию ввода из файла SAMPLE.DAT и переадресацию вывода в файл SAMPLE.BAK:

redirect < sample.dat > sample.bak

И наконец, последняя строка переадресует только вывод данных:

redirect > sample.bak

Стандартный поток ошибок stderr не может быть переадресован.

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

Для того чтобы связать каналом стандартный ввод одной программы и стандартный вывод другой, следует поставить символ вертикальной черты (|):

process1 | process2

Все детали по организации взаимодействия систем ввода-вывода двух программ PROCESS1 и PROCESS2 берет на себя операционная система.

Изменение буфера потока

Потоки stdin, stdout и stdprn буферизуются по умолчанию: данные из них извлекаются, как только буфер переполняется. Потоки stderr и stdaux не буферизуются, если только они не используются в функциях семейств printf() и scanf(): в этих случаях им назначается временный буфер. Буферизацию потоков stderr и stdaux также можно осуществлять с помощью функций setbuf() и setvbuf().

Следует отметить, что системные буферы недоступны для пользователей, но буферы, создаваемые функциями setbuf() и setvbuf(), являются именованными и могут модифицироваться наподобие переменных. Именованные буферы удобно использовать для того, чтобы проконтролировать вводимые или выводимые данные, прежде чем они будут переданы системным функциям и смогут вызвать появление сообщений об ошибках.

Размер создаваемого буфера может быть произвольным. При использовании функции setbuf() размер буфера неявно задается константой BUFSIZ, объявленной в файле STDIO.H. Синтаксис функции выглядит следующим образом:

void setbuf(FILE *имя потока, char *имя_буфера) ;

В следующей программе с помощью данной функции потоку stderr назначается буфер.

/*  *   setbuf.с  Эта программа на языке С демонстрирует, как назначить *  буфер небуферизованному потоку stderr. */

#include <stdio.h>

char cmyoutputbuffer[BUFSIZ];

void main(void)

{

/* Назначение буфера небуферизованному потоку stderr*/ setbuf(stderr, cmyoutputbuffer); /* попробуйте превратить эту строку в комментарий*/

/* Вставка данных в выходной поток */

fputs("Строка данных\n", stderr);

fputs("вставляется в выходной буфер.\n",stderr);

/* "Выталкивание" буфера потока на экран */ fflush(stderr); }

Запустите программу в режиме отладки, и вы увидите, что выводимые строки появятся на экране только после выполнения последней строки, когда содержимое буфера принудительно "выталкивается". Если заключить строку с вызовом функции setbuf() в символы комментария и снова запустить программу в отладчике, то данные, записываемые в поток stderr, не будут буферизоваться, и тогда результат выполнения каждой функции fputs() будет появляться на экране немедленно.

В приводимой ниже программе используется функция setvbuf(),синтаксис которой таков:

int setvbuf(FILE *имя_потока,   char *имя_буфера,   int  тип_буфера, size_t размер_буфера) ;

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

/* *  setvbuf.с

*  Эта программа на языке С демонстрирует использование функции Setvbuf() .

*/

#include <stdio.h>

#define MYBUFSIZE 512

void main(void) {

char ichar, cmybuffer[MYBUFSIZE];

FILE *pfinfile, *pfoutfile;

pfinfile = fopen("sample.in","r");

pfoutfile = fopen("sample.out", "w");

if(setvbuf(pfinfile, cmybuffer, _IOFBF, MYBUFSIZE) != 0) printf("Ошибка выделения буфера для потока pfinfile.\n");

else 

printf("Буфер потока pfinfile создан.\n");   

if(setvbuf(pfoutfile,   NULL,   _IONBF,   0)    !=  0)

printf("Ошибка выделения буфера для потока pfoutfile.\n") ;

else

printf("Поток pfoutfileне имеет буфера.\n");

while (fscanf (pfinfile, "%c", &ichar,) != EOF)

fprintf(pfoutfile, "%c",ichar);    

fclose(pfinfile);

fclose (pfoutfile);      

}

Программа создает именованный буфер для потока pfinfile и запрещает буферизацию потока pfoutfile. В первом случае указан тип буфера _IOFBF(полная буферизация). Если при этом не задать имя буфера, он будет создан автоматически в динамической памяти и так же автоматически удален по завершении работы программы. Во втором случае указан тип _IONBF(нет буфера). В такой ситуации имя буфера и его размер, даже если они заданы, игнорируются.

Закрытие файлов и потоков

Функция fclose () закрывает указанный файл, тогда как функция _fcloseall() закрывает сразу все открытые потоки, кроме стандартных: stdin, stdout, stderr, stdprn и stdaux. Если в программе явно не закрыть поток, он все равно будет автоматически закрыт по завершении программы. Поскольку одновременно можно открыть только ограниченное число потоков, следует своевременно закрывать те из них, которые больше не нужны.

Низкоуровневый ввод-вывод

При низкоуровневом вводе-выводе не происходит ни буферизации, ни форматирования данных. Файлы, открытые на низком уровне, представляются в программе дескрипторами — целочисленными значениями, которые операционная система использует в качестве ссылок на файлы. Для открытия файла предназначена функция _ореn (). Чтобы открыть файл в режиме совместного доступа, следует воспользоваться функцией _sopen().

В табл. 1 перечислены наиболее часто употребляемые в приложениях функции ввода-вывода низкого уровня. Все они объявлены в файле IO.Н.

Данная подсистема ввода-вывода, ориентированная на работу с дисковыми файлами, изначально была создана для операционной системы UNIX. Поскольку комитет ANSI С отказался стандартизировать ее, мы не рекомендуем применять эти функции. В новых проектах предпочтительнее работать со стандартными потоковыми функциями.

Таблица 1. Наиболее часто используемые низкоуровненвые функции ввода-вывода

Функция

Описание

_close() 

Закрывает файл на диске

_lseek() 

Перемещает указатель файла на заданный байт

_ореn () 

Открывает файл на диске

_read()   

Считывает блок данных из файла

_unlike()   

Удаляет файл из каталога

_write()

Записывает блок данных в файл

Ввод-вывод символов

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

Функции getc( ), putc( ), fgetc( ) и fputc( )

Функция getc()  читает один символ из указанного файлового потока:

int ic; ic = getc(stdin);

Вас может удивить, почему переменная ic не объявлена как char. Дело в том, что в прототипе функции getc() указано, что она возвращает значения типа int. Это связано с необходимостью обрабатывать также признак конца файла, который не может быть сохранен в обычной переменной типа char.

Функция getc() преобразовывает читаемый символ из типа char в тип unsigned char и только затем — в int. Такой способ обработки данных гарантирует, что символы с ASCII-кодами больше 127 не будут представлены отрицательными числами. Это позволяет зарезервировать отрицательные значения для нестандартных ситуаций — признаков ошибок или конца файла. Так, например, признаком конца файла традиционно служит значение -1. Правда, стандарт ANSI С гарантирует лишь то, что константа EOF содержит некое отрицательное значение.

Хотя может показаться странным, что функция, предназначенная для ввода символов, возвращает целые числа, в действительности язык С не делает больших различий между типами charи int. Существует четкий алгоритм преобразования целых чисел в символы и наоборот.

Функция getc() читает данные из буфера. Это означает, что управление не будет обратно передано программе до тех пор, пока в указанном потоке не встретится символ новой строки. Функция возвращает только самый первый из обнаруженных ею в буфере символов, другие остаются невостребованными. Таким образом, функцию getc() нельзя использовать для последовательного ввода символов с клавиатур ры, не нажимая при этом каждый раз клавишу [Enter].

Функция putc() записывает символ в файловый поток, представленный указателем файла. Например, чтобы отобразить тот символ, который был введен в предыдущем примере, задайте такую строку:

putc(ic, stdout);

Особенность функций семейства putc() состоит в том, что они возвращают константу eof(обычно она обозначает конец файла) всякий раз при возникновении ошибочных ситуаций. Это может вызвать некоторые недоразумения, хотя, с технической точки зрения, следующий фрагмент совершенно корректен:

if(putc (ic,stdout) == EOF)  printf("Обнаружена ошибка записи в stdout");

И последнее замечание: функции getc() и putc() реализованы и как макросы, и как функции. Макроверсии имеют более высокий приоритет и выполняются в первую очередь. Чтобы изменить такой порядок, следует с помощью директивы препроцессора #undef отменить определение макроса:

#undef getc

Существуют "чистые" функции fgetc () и fputc (), которые выполняют аналогичные действия, но не имеют макросов-"двойников".

Функции getchar( ), putchar( ), _fgetchar( ) n_fputchar()

Функции getchar() и putchar() являются модификациями рассмотренных выше функций getc() и putc(), работающими только cо стандартными потоками ввода (stdin) и вывода (stdout). Рассмотренные в предыдущем параграфе примеры могут быть переписаны с использованием функций getchar() и putchar() следующим образом:

int  ic;  ic  =  getchar();

И

putchar(ic) ;                                .

Эти функции тоже реализованы в виде макросов и в виде функций, а аналогичные им функции, fgetchar() и fputchar(),не имеют макродубликатов.

Функции_getch( ), _getche( ) и_putch( )

Функции _getch(), _getche() и _putch(), объявленные в файле CONIO.H, не соответствуют стандарту ANSI С, поскольку взаимодействуют напрямую с консолью или портом ввода-вывода. Они не работают с буферами, т.е., например, все символы, вводимые с клавиатуры, немедленно возвращаются функцией _getch() в программу. Вывод функции _putch() всегда направляется на консоль.

Функция _getch() воспринимает нажатия тех клавиш, которые игнорируются функцией getchar(), например [PageUp], [PageDown], [Home] и [End], и при этом не требует последующего нажатия клавиши [Enter]. Она работает в режиме без эха, а ее аналог _getche() — с эхом. В случае функциональных и управляющих клавиш эти функции следует вызывать дважды: сначала возвращается 0, а затем — непосредственно код клавиши.

Ввод-вывод строк

Для большинства приложений более важным является ввод-вывод целых строк, а не отдельных символов. Ниже мы познакомимся с основными потоковыми функциями, предназначенными для этих целей. Все они объявлены в файле STDIO.H

Функции gets( ), puts( ), fgets( ) и fputs( )

Предположим, в компании, торгующей лодками, разрабатывается программа, оперирующая строками, каждая из которых состоит из четырех полей: фамилия торгового представителя, отпускная цена, комиссионные и число проданных лодок. Каждое поле отделено от другого символом пробела. С учетом организации данных в файле лучше всего будет рассматривать каждую запись как отдельную строку. Для решения этой задачи наилучшим образом подходит функция fgets(),которая считывает всю строку сразу. Противоположна ей функция fputs (), осуществляющая вывод строки.

Функция fgets () принимает три аргумента: адрес массива, в котором будет сохранена строка, максимальное число символов в строке и указатель потока, из которого выполняется чтение данных. Функция будет считывать символы до тех пор, пока их количество не станет на единицу меньшим, чем указанный предел. Последним всегда записывается символ \0.Если функция fgets() встретит символ новой строки (\n), дальнейшее чтение будет прекращено, а сам символ помещен в массив. Если обнаруживается конец потока, чтение также прекращается.

Предположим, у нас есть файл базы данных SALESMAN.DATсо следующими строками:

Иванов  32767 0.1530    Сергеев 35000 0.1223 Кузьмин 40000 0.1540

Допустим также, что максимальная длина строки с учетом символа \n не должна превышать 40 символов. Приводимая ниже программа считывает записи из файла и направляет их на стандартное устройство вывода:

/* *  fgets.с *   Эта программа на языке С демонстрирует процесс считывания строк с *   помощью функции fgets() и вывода их с помощью функции fputs() */

#include <stdio.h>

#define INULL_CHAR 1

#define IMAX_REC_SIZE 40

void main ()

{

FILE  *pfinfile;

char crecord[IMAX_REC_SIZE +  INULL_CHAR];

pfinfile =  fopen("salesman.dat",   "r") ;

while(fgets(crecord,   IMAX_REC_SIZE  +  INULL_CHAR,   pfinfile)    != NULL) fputs(crecord,  stdout);

fclose(pfinfile);}

Поскольку максимальная длина строки равна 40 символам, следует создать для нее массив, содержащий 41 элемент. Дополнительный элемент необходим для хранения признака конца строки \0. Программа не генерирует самостоятельно никаких разрывов строк при выводе строк на терминал. Тем не менее, структура строк сохраняется такой же, что и в исходном файле, поскольку символы \n читаются и сохраняются в массиве функцией fgets(). Функция fputs() выводит содержимое массива crecord в поток stdout без каких-либо изменений.

Функция gets() отличается от функции fgets () тем, что читает данные только из потока stdin, причем до тех пор, пока не будет нажата клавиша [Enter], не проверяя, достаточно ли в указанном массиве места для размещения всех введенных символов. Символ новой строки \n, генерируемый клавишей [Enter], заменяется символом \0.

Функция puts() выводит данные в стандартный поток вывода stdout и добавляет в конец выводимой строки символ \n, чего не делает функция fputs().

Ввод-вывод целых чисел

В некоторых приложениях бывает необходимо считывать и записывать потоки (в том числе буферизованные) целых чисел. Для этих целей в языке С существуют две функции: getw() и putw().

Функции getw( ) и putw( )

Дополняющие друг друга функции getw()и putw() очень похожи по своему действию на функции getc( ) и putc( ) за тем исключением, что работают с целыми числами, а не символами, и могут использоваться только с файлами, открытыми в двоичном режиме. Следующая программа открывает двоичный файл, записывает в него ряд целых чисел, закрывает файл, а затем вновь открывает его для чтения данных с одновременным выводом их на экран:

/* *   badfile.c *   Эта программа на языке С демонстрирует использование функций getw() и *  putw( ) для ввода-вывода данных из двоичного файла . */

#include <stdio.h> #include <stdlib.h> #define ISIZE 10

void main () {

FILE *pfi;

int ivalue, ivalues [ISIZE], i;

pfi = fopen ("integer.dat","wb") ; if (pfi== NULL) {

printf("Heудалось открыть файл.");

exit(l);

}

for(i = 0; i < ISIZE; i++) {

ivalues[i] = i + 1;

putw(ivalues[i], pfi); }

fclose(pfi);

pfi = fopen("integer.dat",   "wb"); if(pfi == NULL)   {

printf("Heудалось открыть файл.");

exit(l); }

while(feof(pfi)) {

ivalue = getw(pfi);

printf("%3d",ivalue); } }

Посмотрите, какие данные будут выведены на экран при выполнении этой программы, и попытайтесь определить, что было сделано неправильно:

1 2 3 4 5 6 7 8 9 10 -1

Поскольку в цикле while может встретиться признак конца файла EOF, в программе используется функция feof() , сигнализирующая об обнаружении конца файла. Но особенностью этой функции является то, что она не выполняет упреждающего чтения, а лишь проверяет состояние специального флага, который, в свою очередь, устанавливается только после того, как будет непосредственно выполнена операция чтения признака конца файла.

Чтобы исправить ошибку, допущенную в предыдущем примере, применим метод упреждающего чтения. /* *  getwputw.c *  Это исправленная версия предыдущего примера . */

#include <stdio.h> #include <stdlib.h> #define ISIZE 10

void main () {

FILE *pfi;

int ivalue, ivalues [ISIZE], i;

pfi = fopen ("integer .dat","wb");

if (pfi == NULL) {

printf("Heудалось открыть файл.");

exit(l); }

for(i = 0; i < ISIZE; i++) {

ivalues [i]= i + 1;

putw (ivalues [i], pfi); }

fclose (pfi);

pfi = fopen ("integer .dat","rb"); if (pfi== NULL){

printf("Heудалось открыть файл.");

exit(l);

}

ivalue = getw(pfi);

while(feof(pfi)) {

printf("%3d",ivalue)

ivalue = getw(pfi); }

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

Также обратите внимание, что метод упреждающего чтения потребовал измене­ния последовательности инструкций в цикле while. Предположим, цикл выполнен уже девять раз. На девятой итерации переменная ivalue приняла значение 9. На следующей итерации на экран будет выведено 9, а переменной будет присвоено значение 10. Цикл выполнится еще раз, в результате чего на экране отобразится 10 и переменная ivalueпримет значение -1, соответствующее константе EOF. Это вызовет завершение цикла while, поскольку функция feof(} определит конец файла.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]