ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы.



 

Часть 7

                           - 152 -
     
     7. Ввод и вывод
     Средства ввода/вывода не являются составной частью языка
 "с", так что мы не выделяли их в нашем предыдущем изложении.
 Однако реальные программы взаимодействуют со своей окружаю-
 щей средой гораздо более сложным образом, чем мы видели до
 сих пор. В этой главе будет описана "стандартная библиотека
 ввода/вывода", то есть набор функций, разработанных для
 обеспечения стандартной системы ввода/вывода для "с"- прог-
 рамм. Эти функции предназначены для удобства программного
 интерфейса, и все же отражают только те операции, которые
 могут быть обеспечены на большинстве современных операцион-
 ных систем. Процедуры достаточно эффективны для того, чтобы
 пользователи редко чувствовали необходимость обойти их "ради
 эффективности", как бы ни была важна конкретная задача. И,
 наконец, эти процедуры задуманы быть "переносимыми" в том
 смысле, что они должны существовать в совместимом виде на
 любой системе, где имеется язык "с", и что программы, кото-
 рые ограничивают свои взаимодействия с системой возможностя-
 ми, предоставляемыми стандартной библиотекой, можно будет
 переносить с одной системы на другую по существу без измене-
 ний.
     Мы здесь не будем пытаться описать всю библиотеку вво-
 да/вывода; мы более заинтересованы в том, чтобы продемонст-
 рировать сущность написания "с"-программ, которые взаимодей-
 ствуют со своей операционной средой.
 
     7.1. Обращение к стандартной библиотеке
     Каждый исходный файл, который обращается к функции из
 стандартной библиотеки, должен вблизи начала содержать стро-
 ку
 
  #INCLUDE 
 
 в файле STDIO.H определяются некоторые макросы и переменные,
 используемые библиотекой ввода/вывода. Использование угловых
 скобок вместо обычных двойных кавычек - указание компилятору
 искать этот файл в справочнике, содержащем заголовки стан-
 дартной информации (на системе UNIX обычно LUSRLINELUDE).
     Кроме того, при загрузке программы может оказаться необ-
 ходимым указать библиотеку явно; на системе PDP-11 UNIX,
 например, команда компиляции программы имела бы вид:
 
 CC   исходные файлы и т.д. -LS
 
 где -LS указывает на загрузку из стандартной библиотеки.
 
     7.2. Стандартный ввод и вывод - функции GETCHAR и
          PUTCHAR
 
     Самый простой механизм ввода заключается в чтении по од-
 ному символу за раз из "стандартного ввода", обычно с терми-
 нала пользователя, с помощью функции GETCHAR. Функция
 GETCHAR() при каждом к ней обращении возвращает следующий
     
                           - 153 -
     
 вводимый символ. В большинстве сред, которые поддерживают
 язык "с", терминал может быть заменен некоторым файлом с по-
 мощью обозначения < : если некоторая программа PROG исполь-
 зует функцию GETCHAR то командная строка
 
 PROG : если PROG использует PUTCHAR,
 то командная строка
 
 PROG>OUTFILE
 
 приведет к записи стандартного вывода в файл OUTFILE, а не
 на терминал. На системе UNIX можно также использовать поточ-
 ный механизм. Строка
 
  PROG \! ANOTHERPROG
 
 помещает стандартный вывод PROG в стандартный ввод
 ANOTHERPROG. И опять PROG не будет осведомлена об изменении
 направления.
     Вывод, осуществляемый функцией PRINTF, также поступает в
 стандартный вывод, и обращения к PUTCHAR и PRINTF могут пе-
 ремежаться.
     Поразительное количество программ читает только из одно-
 го входного потока и пишет только в один выходной поток; для
 таких программ ввод и вывод с помощью функций GETCHAR,
 PUTCHAR и PRINTF может оказаться вполне адекватным и для на-
 чала определенно достаточным. Это особенно справедливо тог-
     
                           - 154 -
     
 да, когда имеется возможность указания файлов для ввода и
 вывода и поточный механизм для связи вывода одной программы
 с вводом другой. Рассмотрим, например, программу LOWER, ко-
 торая преобразует прописные буквы из своего ввода в строч-
 ные:
 
 #INCLUDE 
 
 MAIN() /* CONVERT INPUT TO LOWER CASE */
 \(
  INT C;
 
  WHILE ((C = GETCHAR()) != EOF)
     PUTCHAR(ISUPPER(C) ? TOLOWER(C) : C);
 \)
 
 "Функции" ISUPPER и TOLOWER на самом деле являются макроса-
 ми, определенными в STDIO.H . Макрос ISUPPER проверяет, яв-
 ляется ли его аргумент буквой из верхнего регистра, и возв-
 ращает ненулевое значение, если это так, и нуль в противном
 случае. Макрос TOLOWER преобразует букву из верхнего регист-
 ра в ту же букву нижнего регистра. Независимо от того, как
 эти функции реализованы на конкретной машине, их внешнее по-
 ведение совершенно одинаково, так что использующие их прог-
 раммы избавлены от знания символьного набора.
     Если требуется преобразовать несколько файлов, то можно
 собрать эти файлы с помощью программы, подобной утилите CAT
 системы UNIX,
 
  CAT FILE1 FILE2 ... \! LOWER>OUTPUT
 
 и избежать тем самым вопроса о том, как обратиться к этим
 файлам из программы. (Программа CAT приводится позже в этой
 главе).
     Кроме того отметим, что в стандартной библиотеке вво-
 да/вывода "функции" GETCHAR и PUTCHAR на самом деле могут
 быть макросами. Это позволяет избежать накладных расходов на
 обращение к функции для обработки каждого символа. В главе 8
 мы продемонстрируем, как это делается.
 
      7.3. Форматный вывод - функция PRINTF
 
     Две функции: PRINTF для вывода и SCANF для ввода (следу-
 ющий раздел) позволяют преобразовывать численные величины в
 символьное представлEние и обратно. Они также позволяют ге-
 нерировать и интерпретировать форматные строки. Мы уже всюду
 в предыдущих главах неформально использовали функцию PRINTF;
 здесь приводится более полное и точное описание. Функция
 
 PRINTF(CONTROL, ARG1, ARG2, ...)
            
                           - 155 -
     
 преобразует, определяет формат и печатает свои аргументы в
 стандартный вывод под управлением строки CONTROL. Управляю-
 щая строка содержит два типа объектов: обычные символы, ко-
 торые просто копируются в выходной поток, и спецификации
 преобразований, каждая из которых вызывает преобразование и
 печать очередного аргумента PRINTF.
     Каждая спецификация преобразования начинается с символа
 % и заканчивается символом преобразования. Между % и симво-
 лом преобразования могут находиться:
 - знак минус, который указывает о выравнивании преобразован-
   ного аргумента по левому краю его поля.
 - Строка цифр, задающая минимальную ширину поля. Преобразо-
   ванное число будет напечатано в поле по крайней мере этой
   ширины, а если необходимо, то и в более широком. Если пре-
   образованный аргумент имеет меньше символов, чем указанная
   ширина поля, то он будет дополнен слева (или справа, если
   было указано выравнивание по левому краю)заполняющими сим-
   волами до этой ширины. Заполняющим символом обычно являет-
   ся пробел, а если ширина поля указывается с лидирующим ну-
   лем, то этим символом будет нуль (лидирующий нуль в данном
   случае не означает восьмеричной ширины поля).
 - Точка, которая отделяет ширину поля от следующей строки
   цифр.
 - Строка цифр (точность), которая указывает максимальное
   число символов строки, которые должны быть напечатаны, или
   число печатаемых справа от десятичной точки цифр для пере-
   менных типа FLOAT или DOUBLE.
 - Модификатор длины L, который указывает, что соответствую-
   щий элемент данных имеет тип LONG, а не INT.
      Ниже приводятся символы преобразования и их смысл:
 
 D - аргумент преобразуется к десятичному виду.
 O - Аргумент преобразуется в беззнаковую восьмеричную форму
   (без лидирующего нуля).
 X - Аргумент преобразуется в беззнаковую шестнадцатеричную
   форму (без лидирующих 0X).
 U - Аргумент преобразуется в беззнаковую десятичную форму.
 C - Аргумент рассматривается как отдельный символ.
 S - Аргумент является строкой: символы строки печатаются до
   тех пор, пока не будет достигнут нулевой символ или не бу-
   дет напечатано количество символов, указанное в специфика-
   ции точности.
 E - Аргумент, рассматриваемый как переменная типа FLOAT или
   DOUBLE, преобразуется в десятичную форму в виде
   [-]M.NNNNNNE[+-]XX, где длина строки из N определяется
   указанной точностью. Точность по умолчанию равна 6.
 F - Аргумент, рассматриваемый как переменная типа FLOAT или
   DOUBLE, преобразуется в десятичную форму в виде
   [-]MMM.NNNNN, где длина строки из N определяется указанной
   точностью. Точность по умолчанию равна 6. отметим, что эта
   точность не определяет количество печатаемых в формате F
   значащих цифр.
     
                           - 156 -
     
 G - Используется или формат %E или %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.
     --------------
 
     Напишите программу, которая будет печатать разумным об-
 разом произвольный ввод. Как минимум она должна печатать
 неграфические символы в восьмеричном или шестнадцатеричном
 виде (в соответствии с принятыми у вас обычаями) и склады-
 вать длинные строки.
 
     7.4. Форматный ввод - функция SCANF
     Осуществляющая ввод функция SCANF является аналогом
 PRINTF и позволяет проводить в обратном направлении многие
 из тех же самых преобразований. Функция
 
 SCANF(CONTROL, ARG1, ARG2, ...)
 
 читает символы из стандартного ввода, интерпретирует их в
 соответствии с форматом, указанном в аргументе CONTROL, и
 помещает результаты в остальные аргументы. Управляющий аргу-
 мент описывается ниже; другие аргументы, каждый из которых
 должен быть указателем, определяют, куда следует поместить
 соответствующим образом преобразованный ввод.
     Управляющая строка обычно содержит спецификации преобра-
 зования, которые используются для непосредственной интерпре-
 тации входных последовательностей. Управляющая строка может
 содержать:
 - пробелы, табуляции или символы новой строки ("символы пус-
   тых промежутков"), которые игнорируются.
     
                           - 157 -
     
 - Обычные символы (не %), которые предполагаются совпадающи-
   ми со следующими отличными от символов пустых промежутков
   символами входного потока.
 - Спецификации преобразования, состоящие из символа %, нео-
   бязательного символа подавления присваивания *, необяза-
   тельного числа, задающего максимальную ширину поля и сим-
   вола преобразования.
     Спецификация преобразования управляет преобразованием
 следующего поля ввода. нормально результат помещается в пе-
 ременную, которая указывается соответствующим аргументом.
 Если, однако , с помощью символа * указано подавление прис-
 ваивания, то это поле ввода просто пропускается и никакого
 присваивания не производится. Поле ввода определяется как
 строка символов, которые отличны от символов простых проме-
 жутков; оно продолжается либо до следующего символа пустого
 промежутка, либо пока не будет исчерпана ширина поля, если
 она указана. Отсюда следует, что при поиске нужного ей вво-
 да, функция SCANF будет пересекать границы строк, поскольку
 символ новой строки входит в число пустых промежутков.
     Символ преобразования определяет интерпретацию поля вво-
 да; согласно требованиям основанной на вызове по значению
 семантики языка "с" соответствующий аргумент должен быть
 указателем. Допускаются следующие символы преобразования:
 D - на вводе ожидается десятичное целое; соответствующий ар-
    гумент должен быть указателем на целое.
 O - На вводе ожидается восьмеричное целое (с лидирующим ну-
    лем или без него); соответствующий аргумент должен быть
    указателем на целое.
 X - На вводе ожидается шестнадцатеричное целое (с лидирующи-
    ми 0X или без них); соответствующий аргумент должен быть
    указателем на целое.
 H - На вводе ожидается целое типа SHORT; соответсвующий ар-
    гумент должен быть указателем на целое типа SHORT.
 C - Ожидается отдельный символ; соответствующий аргумент
    должен быть указателем на символы; следующий вводимый
    символ помещается в указанное место. Обычный пропуск сим-
    волов пустых промежутков в этом случае подавляется; для
    чтения следующего символа, который не является символом
    пустого промежутка, пользуйтесь спецификацией преобразо-
    вания %1S.
 S - Ожидается символьная строка; соответствующий аргумент
    должен быть указателем символов, который указывает на
    массив символов, который достаточно велик для принятия
    строки и добавляемого в конце символа \0.
 F - Ожидается число с плавающей точкой; соответствующий ар-
    гумент должен быть указателем на переменную типа FLOAT.
 Е - символ преобразования E является синонимом для F. Формат
    ввода переменной типа FLOAT включает необязательный знак,
    строку цифр, возможно содержащую десятичную точку и нео-
    бязательное поле экспоненты, состоящее из буквы E, за ко-
    торой следует целое, возможно имеющее знак.
     
                           - 158 -
     
     Перед символами преобразования D, O и X может стоять L,
 которая означает , что в списке аргументов должен находиться
 указатель на переменную типа LONG, а не типа INT. Аналогич-
 но, буква L может стоять перед символами преобразования E
 или F, говоря о том, что в списке аргументов должен нахо-
 диться указатель на переменную типа DOUBLE, а не типа FLOAT.
     Например, обращение
 INT I;
 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". при следующем обращении к любой процеду-
 ре ввода рассмотрение начнется с буквы A. В этих двух приме-
 рах NAME является указателем и, следовательно, перед ним не
 нужно помещать знак &.
     В качестве другого примера перепишем теперь элементарный
 калькулятор из главы 4, используя для преобразования ввода
 функцию SCANF:
 
   #INCLUDE  
   MAIN()    /* RUDIMENTARY DESK CALCULATOR */
   \(
   DOUBLE SUM, V;
   SUM =0;
   WHILE (SCANF("%LF", &V) !=EOF)
        PRINTF("\T%.2F\N", SUM += V);
   \)
 
 выполнение функции SCANF заканчивается либо тогда, когда она
 исчерпывает свою управляющую строку, либо когда некоторый
 элемент ввода не совпадает с управляющей спецификацией. В
 качестве своего значения она возвращает число правильно сов-
 падающих и присвоенных элементов ввода. Это число может быть
     
                           - 159 -
     
 использовано для определения количества найденных элементов
 ввода. при выходе на конец файла возвращается EOF; подчерк-
 нем, что это значение отлично от 0, что следующий вводимый
 символ не удовлетворяет первой спецификации в управляющей
 строке. При следующем обращении к SCANF поиск возобновляется
 непосредственно за последним введенным символом.
     Заключительное предостережение: аргументы функции SCANF
 должны быть указателями. Несомненно наиболее распространен-
 ная ошибка состоит в написании
 
  SCANF("%D", N);
 
 вместо
 
  SCANF("%D", &N);
     
     7.5. Форматное преобразование в памяти
 
 
 
     От функции SCANF и PRINTF происходят функции SSCANF и
 SPRINTF, которые осуществляют аналогичные преобразования, но
 оперируют со строкой, а не с файлом. Обращения к этим функ-
 циям имеют вид:
 
  SPRINTF(STRING, CONTROL, ARG1, ARG2, ...)
  SSCANF(STRING, CONTROL, ARG1, ARG2, ...)
 
 Как и раньше , функция SPRINTF преобразует свои аргументы
 ARG1, ARG2 и т.д. В соответствии с форматом, указанным в
 CONTROL, но помещает результаты в STRING, а не в стандартный
 вывод. KОнечно, строка STRING должна быть достаточно велика,
 чтобы принять результат. Например, если NAME - это символь-
 ный массив, а N - целое, то
 
 SPRINTF(NAME, "TEMP%D", N);
 
 создает в NAME строку вида TEMPNNN, где NNN - значение N.
     Функция SSCANF выполняет обратные преобразования - она
 просматривает строку STRING в соответствии с форматом в ар-
 гументе CONTROL и помещает результирующие значения в аргу-
 менты ARG1, ARG2 и т.д.эти аргументы должны быть указателя-
 ми. В результате обращения
 
 SSCANF(NAME, "TEMP%D", &N);
 
 переменная N получает значение строки цифр, следующих за
 TEMP в NAME.
     Упражнение 7-2.
     --------------
     Перепишите настольный калькулятор из главы 4, используя
 для ввода и преобразования чисел SCANF и/или SSCANF.
            
                           - 160 -
     
     7.6. Доступ к файлам
     Все до сих пор написанные программы читали из стандарт-
 ного ввода и писали в стандартный вывод, относительно кото-
 рых мы предполагали, что они магическим образом предоставле-
 ны программе местной операционной системой.
     Следующим шагом в вопросе ввода-вывода является написа-
 ние программы, работающей с файлом, который не связан зара-
 нее с программой. одной из программ, которая явно демонстри-
 рует потребность в таких операциях, является CAT, которая
 объединяет набор из нескольких именованных файлов в стандар-
 тный вывод. Программа CAT используется для вывода файлов на
 терминал и в качестве универсального сборщика ввода для
 программ, которые не имеют возможности обращаться к файлам
 по имени. Например, команда
 
 CAT X.C.Y.C
 
 печатает содержимое файлов X.C и Y.C в стандартный вывод.
     Вопрос состоит в том, как организовать чтение из имено-
 ванных файлов, т.е., как связать внешние имена, которыми
 мыслит пользователь, с фактически читающими данные операто-
 рами.
 
     Эти правила просты. Прежде чем можно считывать из неко-
 торого файла или записывать в него, этот файл должен быть
 открыт с помощью функции FOPEN из стандартной библиотеки.
 функция FOPEN берет внешнее имя (подобное X.C или Y.C), про-
 водит некоторые обслуживающие действия и переговоры с опера-
 ционной системой (детали которых не должны нас касаться) и
 возвращает внутреннее имя, которое должно использоваться при
 последующих чтениях из файла или записях в него.
     Это внутреннее имя, называемое "указателем файла", фак-
 тически является указателем структуры, которая содержит ин-
 формацию о файле, такую как место размещения буфера, текущая
 позиция символа в буфере, происходит ли чтение из файла или
 запись в него и тому подобное. Пользователи не обязаны знать
 эти детали, потому что среди определений для стандартного
 ввода-вывода, получаемых из файла STDIO.H, содержится опре-
 деление структуры с именем FILE. Единственное необходимое
 для указателя файла описание демонстрируется примером:
     
         FILE *FOPEN(), *FP;
 
     Здесь говорится, что FP является указателем на FILE и
 FOPEN возвращает указатель на FILE. Oбратите внимание, что
 FILE является именем типа, подобным INT, а не ярлыку струк-
 туры; это реализовано как TYPEDEF. (Подробности того, как
 все это работает на системе UNIX, приведены в главе 8).
     Фактическое обращение к функции FOPEN в программе имеет
 вид:
        FP=FOPEN(NAME,MODE);
            
                           - 161 -
     
   Первым аргументом функции 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(C)  PUTC(C,
 STDOUT)
 При работе с файлами для форматного ввода и вывода можно ис-
 пользовать функции FSCANF и FPRINTF. Они идентичны функциям
 SCANF и PRINTF, за исключением того, что первым аргументом
 является указатель файла, определяющий тот файл, который бу-
 дет читаться или куда будет вестись запись; управляющая
 строка будет вторым аргументом.
     
                           - 162 -
     
     Покончив с предварительными замечаниями, мы теперь в
 состоянии написать программу CAT для конкатенации файлов.
 Используемая здесь основная схема оказывается удобной во
 многих программах: если имеются аргументы в командной стро-
 ке, то они обрабатываются последовательно. Если такие аргу-
 менты отсутствуют, то обрабатывается стандартный ввод. Это
 позволяет использовать программу как самостоятельно, так и
 как часть большей задачи.
 
   #INCLUDE 
   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 вызы-
 вается автоматически для каждого открытого файла).
     
                           - 163 -
 
     7.7. Обработка ошибок - STDERR и EXIT
     Обработка ошибок в CAT неидеальна. Неудобство заключает-
 ся в том, что если один из файлов по некоторой причине ока-
 зывается недоступным, диагностическое сообщение об этом пе-
 чатается в конце объединенного вывода. Это приемлемо, если
 вывод поступает на терминал, но не годится, если вывод пос-
 тупает в некоторый файл или через поточный (PIPELINE) меха-
 низм в другую программу.
     Чтобы лучше обрабатывать такую ситуацию, к программе
 точно таким же образом, как STDIN и STDOUT, присоединяется
 второй выходной файл, называемый STDERR. Если это вообще
 возможно, вывод, записанный в файле STDERR, появляется на
 терминале пользователя, даже если стандартный вывод направ-
 ляется в другое место.
     Давайте переделаем программу CAT таким образом, чтобы
 сообщения об ошибках писались в стандартный файл ошибок.
 
    "INCLUDE  
    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 в качетсве возвращаемого значения сви-
 детельствует о том, что все в порядке, а различные ненулевые
 значения являются признаками нормальных ситуаций.
     
                           - 164 -
     
     Функция EXIT вызывает функцию FCLOSE для каждого откры-
 того выходного файла, с тем чтобы вывести всю помещенную в
 буферы выходную информацию, а затем вызывает функцию _EXIT.
 Функция _EXIT приводит к немедленному завершению без очистки
 каких-либо буферов; конечно, при желании к этой функции мож-
 но обратиться непосредственно.
 
      7.8. Ввод и вывод строк
     Стандартная библиотека содержит функцию FGETS, совершен-
 но аналогичную функции GETLINE, которую мы использовали на
 всем протяжении книги. В результате обращения
 
 FGETS(LINE, MAXLINE, FP)
 
 следующая строка ввода (включая символ новой строки) считы-
 вается из файла FP в символьный массив LINE; самое большое
 MAXLINE_1 символ будет прочитан. Результирующая строка за-
 канчивается символом \ 0. Нормально функция FGETS возвращает
 LINE; в конце файла она возвращает NULL. (Наша функция
 GETLINE возвращает длину строки, а при выходе на конец файла
 - нуль).
     Предназначенная для вывода функция FPUTS записывает
 строку (которая не обязана содержать символ новой строки) в
 файл:
 
  FPUTS(LINE, FP)
 
     Чтобы показать, что в функциях типа FGETS и FPUTS нет
 ничего таинственного, мы приводим их ниже, скопированными
 непосредственно из стандартной библиотеки ввода-вывода:
 
    #INCLUDE  
    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) 7 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);
    \)      
                           - 165 -
 
 
     Упражнение 7-3.
     ---------------
     Напишите программу сравнения двух файлов, которая будет
 печатать первую строку и позицию символа, где они различают-
 ся.
     Упражнение 7-4.
     ---------------
     Переделайте программу поиска заданной комбинации симво-
 лов из главы 5 таким образом, чтобы в качестве ввода исполь-
 зовался набор именованных файлов или, если никакие файлы не
 указаны как аргументы, стандартный ввод. Следует ли печатать
 имя файла при нахождении подходящей строки?
     Упражнение 7-5.
     --------------
     Напишите программу печати набора файлов, которая начина-
 ет каждый новый файл с новой страницы и печатает для каждого
 файла заголовок и счетчик текущих страниц.
 
     7.9. Несколько разнообразных функций
     Стандартная библиотека предоставляет множество разнооб-
 разных функций, некоторые из которых оказываются особенно
 полезными. Мы уже упоминали функции для работы со строками:
 STRLEN, STRCPY, STRCAT и STRCMP. Вот некоторые другие.
 
     7.9.1. Проверка вида символов и преобразования
     Некоторые макросы выполняют проверку символов и преобра-
 зования:
 
 
  SALPHA(C) не 0, если "C" алфавитный символ,
      0 - если нет.
  SUPPER(C) Не 0, если "C" буква верхнего регистра,
      0 - если нет.
  SLOWER(C) Не 0, если "C" буква нижнего регистра,
      0 - если нет.
  SDIGIT(C) Не 0, если "C" цифра,
      0 - если нет.
  SSPACL(C) Не 0, если "C" пробел, табуляция
      или новая строка, 0 - если нет.
  OUPPER(C) Преобразует "C" в букву верхнего регистра.
  OLOWER(C) Преобразует "C" в букву нижнего регистра.
 
    7.9.2. Функция UNGETC
     Стандартная библиотека содержит довольно ограниченную
 версию функции UNGETCH, написанной нами в главе 4; она назы-
 вается UNGETC. В результате обращения
 
  UNGETC(C,FP)
 
 символ "C" возвращается в файл FP. Позволяется возвращать в
 каждый файл только один символ. Функция UNGETC может быть
 использована в любой из функций ввода и с макросами типа
 SCANF, GETC или GETCHAR.
     
                           - 166 -
 
     7.9.3. Обращение к системе
     Функция SYSTEM(S) выполняет команду, содержащуюся в сим-
 вольной строке S, и затем возобновляет выполнение текущей
 программы. Содержимое S сильно зависит от используемой опе-
 рационной системы. В качестве тривиального примера, укажем,
 что на системе UNIX строка
 
   SYSTEM("DATE");
 
 приводит к выполнению программы DATE, которая печатает дату
 и время дня.
 
     7.9.4. Управление памятью
     Функция CALLOC весьма сходна с функцией ALLOC, использо-
 ванной нами в предыдущих главах. В результате обращения
 
   CALLOC(N, SIZEOF(OBJCCT))
 
 возвращается либо указатель пространства, достаточного для
 размещения N объектов указанного размера, либо NULL, если
 запрос не может быть удволетворен. Отводимая память инициа-
 лизируется нулевыми значениями.
     Указатель обладает нужным для рассматриваемых объектов
 выравниванием, но ему следует приписывать соответствующий
 тип, как в
 
   CHAR *CALLOC();
   INT *IP;
   IP=(INT*) CALLOC(N,SIZEOF(INT));
 
     Функция CFREE(P) освобождает пространство, на которое
 указывает "P", причем указатель "P" певоначально должен быть
 получен в результате обращения к CALLOC. Здесь нет никаких
 ограничений на порядок освобождения пространства, но будет
 неприятнейшей ошибкой освободить что-нибудь, что не было по-
 лучено обращением к CALLOC.
     Реализация программы распределения памяти, подобной
 CALLOC, в которой размещенные блоки могут освобождаться в
 произвольном порядке, продемонстрирована в главе 8.


?????? ???????????