Средства ввода/вывода не являются составной частью языка "C", такчто мы не выделяли их в нашем предыдущем изложении. Однако реальныепрограммы взаимодействуют со своей окружающей средой гораздо болеесложным образом, чем мы видели до сих пор. В этой главе будет описана"стандартная библиотека ввода/вывода", то есть набор функций,разработанных для обеспечения стандартной системы ввода/вывода для"C"- программ. Эти функции предназначены для удобства программногоинтерфейса, и все же отражают только те операции, которые могут бытьобеспечены на большинстве современных операционных систем. Процедурыдостаточно эффективны для того, чтобы пользователи редко чувствовалинеобходимость обойти их "ради эффективности", как бы ни была важнаконкретная задача.И, наконец, эти процедуры задуманы быть "переносимыми"в том смысле, что они должны существовать в совместимом виде на любойсистеме, где имеется язык "C", и что программы, которые ограничиваютсвои взаимодействия с системой возможностями, предоставляемымистандартной библиотекой, можно будет переносить с одной системы надругую по существу без изменений.
Мы здесь не будем пытаться описать всю библиотеку ввода/вывода; мыболее заинтересованы в том, чтобы продемонстрировать сущностьнаписания "Си"-программ, которые взаимодействуют со своейоперационной средой.
Каждый исходный файл, который обращается к функции из стандартнойбиблиотеки, должен вблизи начала содержать строку
#include <stdio.h>В файле stdio.h Определяются некоторые макросы и переменные,используемые библиотекой ввода/вывода. Использование угловыхскобок вместо обычных двойных кавычек - указание компиляторуискать этот файл в справочнике, содержащем заголовки стандартнойинформации (на системе UNIX обычно lusrlinelude).
Кроме того, при загрузке программы может оказаться необходимымуказать библиотеку явно; на системе pdp-11 UNIX, например, командакомпиляции программы имела бы вид:
сс исходные файлы и т.д. -lsгде -ls указывает на загрузку из стандартной библиотеки.
Самый простой механизм ввода заключается в чтении по одному символуза раз из "стандартного ввода", обычно с терминала пользователя, спомощью функции getchar. Функция getchar() при каждом к ней обращениивозвращает следующий вводимый символ. В большинстве сред, которыеподдерживают язык "Си", терминал может быть заменен некоторым файломс помощью обозначения < : если некоторая программа prog используетфункцию getchar то командная строка
prog < infileприведет к тому, что prog будет читать из файла infile, а не стерминала. Переключение ввода делается таким образом, что самапрограмма prog не замечает изменения; в частности строка"<infile"не включается в командную строку аргументов в argv. Переключениеввода оказывается незаметным и в том случае, когда вывод поступаетиз другой программы посредством поточного (pipe) механизма;командная строка
otherprog | progпрогоняет две программы, otherprog и prog, и организует так, чтостандартным вводом для prog служит стандартный вывод otherprog.
в файле stdio.h), но проверки следует писать в терминах EOF,а не -1, чтобы избежать зависимости от конкретного значения.
Вывод можно осуществлять с помощью функции putchar(c), помещающейсимвол 'c' в "стандартный ввод", который по умолчанию являетсятерминалом. Вывод можно направить в некоторый файл с помощьюобозначения > : если prog использует putchar, то командная строка
prog > outfileприведет к записи стандартного вывода в файл outfile, а не на терминал.На системе UNIX можно также использовать поточный механизм. Строка
prog | anotherprogпомещает стандартный вывод prog в стандартный ввод anotherprog. Иопять prog не будет осведомлена об изменении направления.
Вывод, осуществляемый функцией printf, также поступает в стандартныйвывод, и обращения к putchar и printf могут перемежаться.
Поразительное количество программ читает только из одного входногопотока и пишет только в один выходной поток; для таких программввод и вывод с помощью функций getchar, putchar и printf можетоказаться вполне адекватным и для начала определенно достаточным.Это особенно справедливо тогда, когда имеется возможностьуказания файлов для ввода и вывода и поточный механизм для связивывода одной программы с вводом другой. Рассмотрим, например,программу lower, которая преобразует прописные буквы из своего вводав строчные:
#include <stdio.h> main() /* convert input to lower case */
{
int c;
while ((c = getchar()) != EOF)
putchar(isupper(c) ? tolower(c) : c); }
"Функции" isupper и tolower на самом деле являются макросами,определенными в stdio.h . Макрос isupper проверяет, является лиего аргумент буквой из верхнего регистра, и возвращает ненулевоезначение, если это так, и нуль в противном случае. Макрос tolowerпреобразует букву из верхнего регистра в ту же букву нижнего регистра.Независимо от того, как эти функции реализованы на конкретной машине,их внешнее поведение совершенно одинаково, так что использующие ихпрограммы избавлены от знания символьного набора.
Если требуется преобразовать несколько файлов, то можно собрать этифайлы с помощью программы, подобной утилите сат системы UNIX,
cat file1 file2 ... | lower > outputи избежать тем самым вопроса о том, как обратиться к этим файламиз программы. (программа сат приводится позже в этой главе).
Кроме того отметим, что в стандартной библиотеке ввода/вывода"функции" getchar и putchar на самом деле могут быть макросами.Это позволяет избежать накладных расходов на обращение к функциидля обработки каждого символа. В главе 8 мы продемонстрируем, как это делается.
Две функции: printf для вывода и scanf для ввода (следующий раздел)позволяют преобразовывать численные величины в символьноепредставление и обратно. Они также позволяют генерировать иинтерпретировать форматные строки. Мы уже всюду в предыдущихглавах неформально использовали функцию printf; здесь приводитсяболее полное и точное описание. Функция
printf(control, arg1, arg2, ...)поток, и спецификации преобразований, каждая из которыхвызывает преобразование и печать очередного аргумента printf.
Каждая спецификация преобразования начинается с символа % изаканчивается символом преобразования. Между % и символомпреобразования могут находиться:
Ниже приводятся символы преобразования и их смысл:
Большинство из форматных преобразований очевидно и былопроиллюстрировано в предыдущих главах. Единственным исключением являетсято, как точность взаимодействует со строками. Следующая таблицадемонстрирует влияние задания различных спецификаций на печать"hello, world" (12 символов). Мы поместили двоеточия вокругкаждого поля для того, чтобы вы могли видеть его протяженность.
:%10s: :hello, world: :%10-s: :hello, world: :%20s: : hello, world: :%-20s: :hello, world : :%20.10s: : hello, wor: :%-20.10s: :hello, wor : :%.10s: :hello, wor:
товозникнет путаница и вы получите бессмысленные результаты.
Осуществляющая ввод функция scanf является аналогом printf и позволяетпроводить в обратном направлении многие из тех же самыхпреобразований. Функция
scanf(control, arg1, arg2, ...)каждый из которых должен быть указателем, определяют,куда следует поместить соответствующим образом преобразованный ввод.
Управляющая строка обычно содержит спецификации преобразования,которые используются для непосредственной интерпретации входныхпоследовательностей. Управляющая строка может содержать:
Спецификация преобразования управляет преобразованием следующего поляввода. Нормально результат помещается в переменную, которая указываетсясоответствующим аргументом. Если, однако, с помощью символа * указаноподавление присваивания, то это поле ввода просто пропускается иникакого присваивания не производится. Поле ввода определяется какстрока символов, которые отличны от символов простых промежутков;оно продолжается либо до следующего символа пустого промежутка, либопока не будет исчерпана ширина поля, если она указана. Отсюдаследует, что при поиске нужного ей ввода, функция scanf будетпересекать границы строк, поскольку символ новой строки входит вчисло пустых промежутков.
Символ преобразования определяет интерпретацию поля ввода; согласнотребованиям основанной на вызове по значению семантики языка "Си"соответствующий аргумент должен быть указателем. Допускаются следующиесимволы преобразования:
Перед символами преобразования d, о и х может стоять l, которая означает, что в списке аргументов должен находиться указатель на переменнуютипа long, а не типа int. Аналогично, буква l можетстоять перед символами преобразования е или f, говоря о том, что всписке аргументов должен находиться указатель на переменную типаdouble, а не типа float.
Например, обращение
int 1; float x; char name[50]; scanf("&d %f %s", &i, &x, name);
со строкой на вводе
25 54.32e-1 thompsonприводит к присваиванию i значения 25, x - значения 5.432 и name -строки "thompson", надлежащим образом законченной символом \ 0.Эти три поля ввода можно разделить столькими пробелами, табуляциямии символами новых строк, сколько вы пожелаете. Обращение
int i; float x; char name[50]; scanf("%2d %f %*d %2s", &i, &x, name);
с вводом
56789 0123 45a72присвоит i значение 56, x - 789.0, пропустит 0123 и поместит вname строку "45". При следующем обращении к любой процедуре вводарассмотрение начнется с буквы а. В этих двух примерах nameявляется указателем и, следовательно, перед ним не нужно помещатьзнак &.
В качестве другого примера перепишем теперь элементарный калькуляториз главы 4 ,используя для преобразования ввода функцию scanf:
#include <stdio.h> main() /* rudimentary desk calculator */
{
double sum, v;
sum =0;
while (scanf("%lf", &v) !=EOF)
printf("\\t%.2f\n", sum += v); }
Выполнение функции scanf заканчивается либо тогда, когда онаисчерпывает свою управляющую строку, либо когда некоторый элементввода не совпадает с управляющей спецификацией. В качестве своегозначения она возвращает число правильно совпадающих и присвоенныхэлементов ввода. Это число может быть использовано для определенияколичества найденных элементов ввода. При выходе на конец файлавозвращается EOF; подчеркнем, что это значение отлично от 0, чтоследующий вводимый символ не удовлетворяет первой спецификации вуправляющей строке. При следующем обращении к scanf поисквозобновляется непосредственно за последним введенным символом.
Заключительное предостережение: аргументы функции scanf должны бытьуказателями. Несомненно наиболее распространенная ошибкасостоит в написании
scanf("%d", n);
вместо
scanf("%d", &n);
От функции scanf и printf происходят функции sscanf и sprintf,которые осуществляют аналогичные преобразования, но оперируют сострокой, а не с файлом. Обращения к этим функциям имеют вид:
sprintf(string, control, arg1, arg2, ...) sscanf(string, control, arg1, arg2, ...)Как и раньше, функция sprintf преобразует свои аргументы arg1,arg2 и т.д. в соответствии с форматом, указанным в control, нопомещает результаты в string, а не в стандартный вывод. Конечно,строка string должна быть достаточно велика, чтобы принять результат.Например, если name - это символьный массив, а n - целое, то
sprintf(name, "temp%d", n);создает в name строку вида tempnnn, где nnn - значение n.
Функция sscanf выполняет обратные преобразования - онапросматривает строку string в соответствии с форматом в аргументеcontrol и помещает результирующие значения в аргументы arg1, arg2и т.д.эти аргументы должны быть указателями. В результате обращения
sscanf(name, "temp%d", &n);переменная n получает значение строки цифр, следующих за темр в name.
Все до сих пор написанные программы читали из стандартного ввода иписали в стандартный вывод, относительно которых мы предполагали, чтоони магическим образом предоставлены программе местной операционнойсистемой.
Следующим шагом в вопросе ввода-вывода является написание программы,работающей с файлом, который не связан заранее с программой. Одной изпрограмм, которая явно демонстрирует потребность в таких операциях,является сат, которая об'единяет набор из нескольких именованныхфайлов в стандартный вывод. Программа сат используется для выводафайлов на терминал и в качестве универсального сборщика ввода дляпрограмм, которые не имеют возможности обращаться к файлам по имени.Например, команда
cat x.c.y.cПечатает содержимое файлов x.c и y.y В стандартный вывод.
Вопрос состоит в том, как организовать чтение из именованныхфайлов, т.е., как связать внешние имена, которыми мыслит пользователь,с фактически читающими данные операторами.
Эти правила просты. Прежде чем можно считывать из некоторого файла илизаписывать в него, этот файл должен быть открыт с помощью функции fopen возвращает внутреннее имя, которое должно использоватьсяпри последующих чтениях из файла или записях в него.
Это внутреннее имя, называемое "указателем файла", фактическиявляется указателем структуры, которая содержит информацию о файле,такую как место размещения буфера, текущая позиция символа в буфере,происходит ли чтение из файла или запись в него и тому подобное.Пользователи не обязаны знать эти детали, потому что средиопределений для стандартного ввода-вывода, получаемых из файлаstdio.h, содержится определение структуры с именем FILE.Единственное необходимое для указателя файла описаниедемонстрируется примером:
FILE *fopen(), *fp;
Здесь говорится, что fp является указателем на FILE и fopen возвращаетуказатель на FILE. Обратите внимание, что FILE является именем типа,подобным int, а не ярлыку структуры; это реализовано как typedef.(Подробности того, как все это работает на системе UNIX, приведеныв главе 8 ).
Фактическое обращение к функции fopen в программе имеет вид:
fp = fopen( name, mode);Допустимыми режимами являются: чтение ("r"),запись ("w") и добавление ("a").
Если вы откроете файл, который еще не сущетвует, для записи илидобавления, то такой файл будет создан (если это возможно).Открытие существующего файла на запись приводит к отбрасыванию егостарого содержимого. Попытка чтения несуществующего файла являетсяощибкой. Ошибки могут быть обусловлены и другими причинами (например,попыткой чтения из файла, не имея на то разрешения). При наличиикакой-либо ошибки функция возвращает нулевое значение указателя NULL(которое для удобства также определяется в файле stdio.h).
Другой необходимой вещью является способ чтения или записи, если файлуже открыт. Здесь имеется несколько возможностей, из которых getcи putc являются простейшими.функция getc возвращает следующий символиз файла; ей необходим указатель файла, чтобы знать, из какого файлачитать. Таким образом,
c=getc(fp)помещает в "C" следующий символ из файла, указанного посредством fp,и EOF, если достигнут конец файла.
Функция putc, являющаяся обращением к функции getc,
putc(c,fp)помещает символ "C" в файл fp и возвращает "C". Подобно функциямgetchar и putchar, getc и putc могут быть макросами, а не функциями.
указатели файлов называются stdin, stdout и stderr.Обычно все эти указатели связаны с терминалом, но stdin и stdout могутбыть перенаправлены на файлы или в поток (pipe), как описывалось вразделе 7.2.
Функции getchar и putchar могут быть определены в терминалах getc,putc, stdin и stdout следующим образом:
#define getchar() getc(stdin) #define putchar(с) putc(с, stdout)определяющий тот файл, который будетчитаться или куда будет вестись запись; управляющая строка будетвторым аргументом.
Покончив с предварительными замечаниями, мы теперь в состояниинаписать программу cat для конкатенации файлов. Используемая здесьосновная схема оказывается удобной во многих программах: еслиимеются аргументы в командной строке, то они обрабатываютсяпоследовательно. Если такие аргументы отсутствуют, то обрабатываетсястандартный ввод. Это позволяет использовать программу каксамостоятельно, так и как часть большей задачи.
#include <stdio.h> main(argc, argv) /*cat: concatenate files*/
int argc; char *argv[]; {
FILE *fp, *fopen();
if(argc==1) /*no args; copy standard input*/
filecopy(stdin);
else
while (--argc > 0)
if ((fp=fopen(*++argv,"r"))==NULL) {
printf("cat:can't open %\n",*argv);
break;
} else {
filecopy(fp);
fclose(fp);
} } filecopy(fp) /*copy file fp to standard output*/
FILE *fp; { int c;
while ((c=getc(fp)) !=EOF)
putc(c, stdout); }
Указатели файлов stdin и stdout заранее определены в библиотекеввода-вывода как стандартный ввод и стандартный вывод; они могутбыть использованы в любом месте, где можно использовать об'ект типаFILE*. Они, однако, являются константами, а не переменными, так что непытайтесь им что-либо присваивать.
Функция fclose является обратной по отношению к fopen; она разрываетсвязь между указателем файла и внешним именем, установленную функциейfopen, и высвобождает указатель файла для другого файла.большинствоОперационных систем имеют некоторые ограничения на число одновременнооткрытых файлов, которыми может распоряжаться программа. Поэтому, токак мы поступили в cat, освободив не нужные нам более об'екты,является хорошей идеей. Имеется и другая причина для примененияфункции fclose к выходному файлу - она вызывает выдачу информации избуфера, в котором putc собирает вывод. (при нормальном завершенииработы программы функция fclose вызывается автоматически для каждогооткрытого файла).
Обработка ошибок в сат неидеальна. Неудобство заключается в том, чтоесли один из файлов по некоторой причине оказывается недоступным,диагностическое сообщение об этом печатается в конце об'единенноговывода. Это приемлемо, если вывод поступает на терминал, но негодится, если вывод поступает в некоторый файл или через поточный(pipeline) механизм в другую программу.
Чтобы лучше обрабатывать такую ситуацию, к программе точно таким жеобразом, как stdin и stdout, присоединяется второй выходной файл,называемый stderr. Если это вообще возможно, вывод, записанный в файлеstderr, появляется на терминале пользователя, даже если стандартныйвывод направляется в другое место.
Давайте переделаем программу сат таким образом, чтобы сообщения обошибках писались в стандартный файл ошибок.
#include <stdio.h> main(argc,argv) /*cat: concatenate files*/
int argc; char *argv[]; {
FILE *fp, *fopen();
if(argc==1) /*no args; copy standard input*/
filecopy(stdin);
else
while (--argc > 0)
if((fp=fopen(*++argv,"r#))==NULL) {
printf(stderr,
"cat: can't open,%s\n", argv);
exit(1);
} else {
filecopy(fp);
}
exit(0); }
Программа сообщает об ошибках двумя способами. Диагностическоесообщение, выдаваемое функцией fprintf, поступает в stderr и,таким образом, оказывается на терминале пользователя, а не исчезает впотоке (pipeline) или в выходном файле.
Программа также использует функцию exit из стандартной библиотеки,обращение к которой вызывает завершение выполнения программы. Аргументфункции exit доступен любой программе, обращающейся к данной функции,так что успешное или неудачное завершение данной программы может бытьпроверено другой программой, использующей эту в качестве подзадачи. Посоглашению величина 0 в качетсве возвращаемого значения свидетельствуето том, что все в порядке, а различные ненулевые значения являютсяпризнаками нормальных ситуаций.
Функция exit вызывает функцию fclose для каждого открытого выходногофайла, с тем чтобы вывести всю помещенную в буферы выходную информацию,а затем вызывает функцию _exit. Функция _exit приводит к немедленномузавершению без очистки каких-либо буферов; конечно, при желании к этойфункции можно обратиться непосредственно.
Стандартная библиотека содержит функцию fgets, совершенно аналогичнуюфункции getline, которую мы использовали на всем протяжении книги. Врезультате обращения
fgets(line, maxline, fp)fgets возвращает line; в конце файла она возвращает NULL.(Наша функция getline возвращает длину строки, а при выходе на конецфайла - нуль).
Предназначенная для вывода функция fputs записывает строку (которая необязана содержать символ новой строки) в файл:
fputs(line, fp)
Чтобы показать, что в функциях типа fgets и fputs нет ничеготаинственного, мы приводим их ниже, скопированными непосредственно изстандартной библиотеки ввода-вывода:
<stdio.h> char *fgets(s,n,iop) /*get at most n chars from iop*/
char *s; int n; register FILE *iop; {
register int c;
register char *cs;
cs = s;
while(--n>0&&(c=getc(iop)) !=EOF)
if ((*cs++ = c)=='\n')
break;
*cs = '\0';
return((c==EOF && cs==s) ? NULL : s); } fputs(s,iop) /*put string s on fils iop*/
register char *s; register FILE *iop; {
register int c;
while (c = *s++)
putc(c,iop); }
Стандартная библиотека предоставляет множество разнообразных функций,некоторые из которых оказываются особенно полезными. Мы уже упоминалифункции для работы со строками: strlen, strcpy, strcat и strcmp. Вотнекоторые другие.
Некоторые макросы выполняют проверку символов и преобразования:
Стандартная библиотека содержит довольно ограниченную версию функцииungetch, написанной нами в главе 4 ;она называется ungetc. В результатеобращения
ungetc(c,fp)
примера, укажем, что на системе UNIX строка
system("date");
приводит к выполнению программы date, которая печатает дату и время дня.
Функция calloc весьма сходна с функцией alloc, использованной нами впредыдущих главах. В результате обращения
calloc(n, sizeof(objcct))возвращается либо указатель пространства, достаточного для размещенияn об'ектов указанного размера, либо NULL, если запрос не может бытьудволетворен. Отводимая память инициализируется нулевыми значениями.
Указатель обладает нужным для рассматриваемых об'ектов выравниванием,но ему следует приписывать соответствующий тип, как в
char *calloc(); int *ip; ip=(int*) calloc(n,sizeof(int));
Функция cfree(р) освобождает пространство, на которое указывает "p",причем указатель "p" певоначально должен быть получен в результатеобращения к calloc. Здесь нет никаких ограничений на порядокосвобождения пространства, но будет неприятнейшей ошибкой освободитьчто-нибудь, что не было получено обращением к calloc.
Реализация программы распределения памяти, подобной calloc, в которойразмещенные блоки могут освобождаться в произвольном порядке,продемонстрирована в главе 8 .
[Назад] [Содержание] [Вперед]
| Главная |