Kernighan, B. W. and Ritchie, D. M. "The 'C' Programming Language"; Chapter 7

Ввод и вывод.

Средства ввода/вывода не являются составной частью языка "C", такчто мы не выделяли их в нашем предыдущем изложении. Однако реальныепрограммы взаимодействуют со своей окружающей средой гораздо болеесложным образом, чем мы видели до сих пор. В этой главе будет описана"стандартная библиотека ввода/вывода", то есть набор функций,разработанных для обеспечения стандартной системы ввода/вывода для"C"- программ. Эти функции предназначены для удобства программногоинтерфейса, и все же отражают только те операции, которые могут бытьобеспечены на большинстве современных операционных систем. Процедурыдостаточно эффективны для того, чтобы пользователи редко чувствовалинеобходимость обойти их "ради эффективности", как бы ни была важнаконкретная задача.И, наконец, эти процедуры задуманы быть "переносимыми"в том смысле, что они должны существовать в совместимом виде на любойсистеме, где имеется язык "C", и что программы, которые ограничиваютсвои взаимодействия с системой возможностями, предоставляемымистандартной библиотекой, можно будет переносить с одной системы надругую по существу без изменений.

Мы здесь не будем пытаться описать всю библиотеку ввода/вывода; мыболее заинтересованы в том, чтобы продемонстрировать сущностьнаписания "Си"-программ, которые взаимодействуют со своейоперационной средой.

Содержание

7.1. Обращение к стандартной библиотеке.
7.2. Стандартный ввод и вывод - функции getchar и putchar
7.3. Форматный вывод - функция printf
7.4. Форматный ввод - функция scanf.
7.5. Форматное преобразование в памяти.
7.6. Доступ к файлам.
7.7. Обработка ошибок - stderr и exit.
7.8. Ввод и вывод строк.
7.9. Несколько разнообразных функций
7.9.1. Проверка вида символов и преобразования.
7.9.2. Функция ungetc.
7.9.3. Обращение к системе.
7.9.4. Управление памятью.


Обращение к стандартной библиотеке.

Каждый исходный файл, который обращается к функции из стандартнойбиблиотеки, должен вблизи начала содержать строку

 #include <stdio.h>
В файле stdio.h Определяются некоторые макросы и переменные,используемые библиотекой ввода/вывода. Использование угловыхскобок вместо обычных двойных кавычек - указание компиляторуискать этот файл в справочнике, содержащем заголовки стандартнойинформации (на системе UNIX обычно lusrlinelude).

Кроме того, при загрузке программы может оказаться необходимымуказать библиотеку явно; на системе pdp-11 UNIX, например, командакомпиляции программы имела бы вид:

 сс исходные файлы и т.д. -ls
где -ls указывает на загрузку из стандартной библиотеки.


Стандартный ввод и вывод - функции getchar и putchar

Самый простой механизм ввода заключается в чтении по одному символуза раз из "стандартного ввода", обычно с терминала пользователя, спомощью функции getchar. Функция getchar() при каждом к ней обращениивозвращает следующий вводимый символ. В большинстве сред, которыеподдерживают язык "Си", терминал может быть заменен некоторым файломс помощью обозначения < : если некоторая программа prog используетфункцию getchar то командная строка

 prog < infile
приведет к тому, что prog будет читать из файла infile, а не стерминала. Переключение ввода делается таким образом, что самапрограмма prog не замечает изменения; в частности строка"<infile"не включается в командную строку аргументов в argv. Переключениеввода оказывается незаметным и в том случае, когда вывод поступаетиз другой программы посредством поточного (pipe) механизма;командная строка
 otherprog | prog
прогоняет две программы, otherprog и prog, и организует так, чтостандартным вводом для prog служит стандартный вывод otherprog.

Функция getchar возвращает значение EOF, когда она попадает наконец файла, какой бы ввод она при этом не считывала. Стандартнаябиблиотека полагает символическую константу EOF равной -1 (посредством#define в файле 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

Две функции: printf для вывода и scanf для ввода (следующий раздел)позволяют преобразовывать численные величины в символьноепредставление и обратно. Они также позволяют генерировать иинтерпретировать форматные строки. Мы уже всюду в предыдущихглавах неформально использовали функцию printf; здесь приводитсяболее полное и точное описание. Функция

 printf(control, arg1, arg2, ...)
преобразует, определяет формат и печатает свои аргументы встандартный вывод под управлением строки control. Управляющая строкасодержит два типа об'ектов: обычные символы, которые просто копируютсяв выходной поток, и спецификации преобразований, каждая из которыхвызывает преобразование и печать очередного аргумента printf.

Каждая спецификация преобразования начинается с символа % изаканчивается символом преобразования. Между % и символомпреобразования могут находиться:

  • знак минус, который указывает о выравнивании преобразованногоаргумента по левому краю его поля.

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

  • Точка, которая отделяет ширину поля от следующей строки цифр.

  • Строка цифр (точность), которая указывает максимальное числосимволов строки, которые должны быть напечатаны, или число печатаемыхсправа от десятичной точки цифр для переменных типа float илиdouble.

  • Модификатор длины l, который указывает, что соответствующийэлемент данных имеет тип long, а не int.

    Ниже приводятся символы преобразования и их смысл:

    d
    аргумент преобразуется к десятичному виду.

    o
    аргумент преобразуется в беззнаковую восьмеричную форму(без лидирующего нуля).

    x
    аргумент преобразуется в беззнаковую шестнадцатеричную форму(без лидирующих 0х).

    u
    аргумент преобразуется в беззнаковую десятичную форму.

    c
    аргумент рассматривается как отдельный символ.

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

    e
    аргумент, рассматриваемый как переменная типа float или double,преобразуется в десятичную форму в виде [-]m.nnnnnne[+-]хх, гдедлина строки из n определяется указанной точностью. Точность поумолчанию равна 6.

    f
    аргумент, рассматриваемый как переменная типа float или double,преобразуется в десятичную форму в виде [-]mmm.nnnnn, где длинастроки из n определяется указанной точностью. Точность поумолчанию равна 6.Отметим, что эта точность не определяетколичество печатаемых в формате f значащих цифр.

    g
    используется или формат %е или %f, какой короче;незначащие нули не печатаются.Если идущий за % символ не является символом преобразования,то печатается сам этот символ; следовательно,символ % можнонапечатать, указав %%.

    Большинство из форматных преобразований очевидно и былопроиллюстрировано в предыдущих главах. Единственным исключением являетсято, как точность взаимодействует со строками. Следующая таблицадемонстрирует влияние задания различных спецификаций на печать"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:

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

    Упражнение 7-1.
    Напишите программу, которая будет печатать разумным образом произвольныйввод. Как минимум она должна печатать неграфические символы ввосьмеричном или шестнадцатеричном виде (в соответствии с принятымиу вас обычаями) и складывать длинные строки.


    Форматный ввод - функция scanf.

    Осуществляющая ввод функция scanf является аналогом printf и позволяетпроводить в обратном направлении многие из тех же самыхпреобразований. Функция

     scanf(control, arg1, arg2, ...)
    читает символы из стандартного ввода, интерпретирует их в соответствиис форматом, указанном в аргументе control, и помещает результаты востальные аргументы. Управляющий аргумент описывается ниже; другиеаргументы, каждый из которых должен быть указателем, определяют,куда следует поместить соответствующим образом преобразованный ввод.

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

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

    Спецификация преобразования управляет преобразованием следующего поляввода. Нормально результат помещается в переменную, которая указываетсясоответствующим аргументом. Если, однако, с помощью символа * указаноподавление присваивания, то это поле ввода просто пропускается иникакого присваивания не производится. Поле ввода определяется какстрока символов, которые отличны от символов простых промежутков;оно продолжается либо до следующего символа пустого промежутка, либопока не будет исчерпана ширина поля, если она указана. Отсюдаследует, что при поиске нужного ей ввода, функция scanf будетпересекать границы строк, поскольку символ новой строки входит вчисло пустых промежутков.

    Символ преобразования определяет интерпретацию поля ввода; согласнотребованиям основанной на вызове по значению семантики языка "Си"соответствующий аргумент должен быть указателем. Допускаются следующиесимволы преобразования:

    d
    на вводе ожидается десятичное целое; соответствующий аргумент долженбыть указателем на целое.

    o
    на вводе ожидается восьмеричное целое (с лидирующим нулем или без него);соответствующий аргумент должен быть указателем на целое.

    x
    на вводе ожидается шестнадцатеричное целое (с лидирующими 0х или безних); соответствующий аргумент должен быть указателем на целое.

    h
    на вводе ожидается целое типа short; соответсвующий аргумент долженбыть указателем на целое типа short.

    c
    ожидается отдельный символ; соответствующий аргумент должен бытьуказателем на символы; следующий вводимый символ помещается вуказанное место. Обычный пропуск символов пустых промежутков в этомслучае подавляется; для чтения следующего символа, который неявляется символом пустого промежутка, пользуйтесь спецификациейпреобразования %1s.

    s
    ожидается символьная строка; соответствующий аргумент должен бытьуказателем символов, который указывает на массив символов, которыйдостаточно велик для принятия строки и добавляемого в конце символа \0.

    f
    ожидается число с плавающей точкой; соответствующий аргумент долженбыть указателем на переменную типа float.

    e
    символ преобразования е является синонимом для f. Формат вводапеременной типа float включает необязательный знак, строку цифр,возможно содержащую десятичную точку и необязательное поле экспоненты,состоящее из буквы е, за которой следует целое, возможно имеющее знак.

    Перед символами преобразования 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.
    Упражнение 7-2.
    Перепишите настольный калькулятор изглавы 4, используядля ввода и преобразования чисел scanf и/или sscanf.


    Доступ к файлам.

    Все до сих пор написанные программы читали из стандартного ввода иписали в стандартный вывод, относительно которых мы предполагали, чтоони магическим образом предоставлены программе местной операционнойсистемой.

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

     cat x.c.y.c
    Печатает содержимое файлов x.c и y.y В стандартный вывод.

    Вопрос состоит в том, как организовать чтение из именованныхфайлов, т.е., как связать внешние имена, которыми мыслит пользователь,с фактически читающими данные операторами.

    Эти правила просты. Прежде чем можно считывать из некоторого файла илизаписывать в него, этот файл должен быть открыт с помощью функцииfopenиз стандартной библиотеки. Функция fopen берет внешнее имя(подобное x.c или y.c), проводит некоторые обслуживающие действияи переговоры с операционной системой (детали которых не должны наскасаться) и возвращает внутреннее имя, которое должно использоватьсяпри последующих чтениях из файла или записях в него.

    Это внутреннее имя, называемое "указателем файла", фактическиявляется указателем структуры, которая содержит информацию о файле,такую как место размещения буфера, текущая позиция символа в буфере,происходит ли чтение из файла или запись в него и тому подобное.Пользователи не обязаны знать эти детали, потому что средиопределений для стандартного ввода-вывода, получаемых из файлаstdio.h, содержится определение структуры с именем FILE.Единственное необходимое для указателя файла описаниедемонстрируется примером:

    FILE *fopen(), *fp;

    Здесь говорится, что fp является указателем на FILE и fopen возвращаетуказатель на FILE. Обратите внимание, что FILE является именем типа,подобным int, а не ярлыку структуры; это реализовано как typedef.(Подробности того, как все это работает на системе UNIX, приведеныв главе 8).

    Фактическое обращение к функции fopen в программе имеет вид:

    fp = fopen( name, mode);
    Первым аргументом функции fopen является "имя" файла, котороезадается в виде символьной строки. Второй аргумент 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)
    При работе с файлами для форматного ввода и вывода можноиспользовать функции fscanf и fprintf. Они идентичны функциямscanf и printf, за исключением того, что первым аргументомявляется указатель файла, определяющий тот файл, который будетчитаться или куда будет вестись запись; управляющая строка будетвторым аргументом.

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


    Обработка ошибок - stderr и exit.

    Обработка ошибок в сат неидеальна. Неудобство заключается в том, чтоесли один из файлов по некоторой причине оказывается недоступным,диагностическое сообщение об этом печатается в конце об'единенноговывода. Это приемлемо, если вывод поступает на терминал, но негодится, если вывод поступает в некоторый файл или через поточный(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)
    следующая строка ввода (включая символ новой строки) считывается изфайла fp в символьный массив line; самое большое maxline_1 символ будетпрочитан. Результирующая строка заканчивается символом \ 0. Нормальнофункция fgets возвращает line; в конце файла она возвращает NULL.(Наша функция getline возвращает длину строки, а при выходе на конецфайла - нуль).

    Предназначенная для вывода функция fputs записывает строку (которая необязана содержать символ новой строки) в файл:

     fputs(line, fp)

    Чтобы показать, что в функциях типа fgets и fputs нет ничеготаинственного, мы приводим их ниже, скопированными непосредственно изстандартной библиотеки ввода-вывода:

     #include <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); }
    Упражнение 7-3.
    Напишите программу сравнения двух файлов, котораябудет печатать первую строку и позицию символа, где они различаются.

    Упражнение 7-4.
    Переделайте программу поиска заданной комбинациисимволов из главы 5таким образом, чтобы в качестве ввода использовалсянабор именованных файлов или, если никакие файлы не указаны какаргументы, стандартный ввод. Следует ли печатать имя файла принахождении подходящей строки?

    Упражнение 7-5.
    Напишите программу печати набора файлов, котораяначинает каждый новый файл с новой страницы и печатает для каждогофайла заголовок и счетчик текущих страниц.


    Несколько разнообразных функций

    Стандартная библиотека предоставляет множество разнообразных функций,некоторые из которых оказываются особенно полезными. Мы уже упоминалифункции для работы со строками: strlen, strcpy, strcat и strcmp. Вотнекоторые другие.

    Проверка вида символов и преобразования.

    Некоторые макросы выполняют проверку символов и преобразования:

    • isalpha(c) не 0, если "c" алфавитный символ, 0 - если нет.
    • isupper(c) не 0, если "c" буква верхнего регистра, 0 - если нет.
    • islower(c) не 0, если "c" буква нижнего регистра, 0 - если нет.
    • isdigit(c) не 0, если "c" цифра, 0 - если нет.
    • isspacl(c) не 0, если "c" пробел, табуляция или новая строка, 0 - если нет.
    • toupper(c) преобразует "c" в букву верхнего регистра.
    • tolower(c) преобразует "c" в букву нижнего регистра.

    Функция ungetc.

    Стандартная библиотека содержит довольно ограниченную версию функцииungetch, написанной нами в главе 4;она называется ungetc. В результатеобращения

     ungetc(c,fp)
    символ "c" возвращается в файл fp. Позволяется возвращать в каждый файлтолько один символ. Функция ungetc может быть использована в любой изфункций ввода и с макросами типа scanf, getc или getchar.

    Обращение к системе.

    Функция system(s) выполняет команду, содержащуюся в символьной строкеs, и затем возобновляет выполнение текущей программы. Содержимое sсильно зависит от используемой операционной системы. В качестветривиального примера, укажем, что на системе 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.