STRUCT TNODE *RIGHT; /* RIGHT CHILD */
\) TREENODE, *TREEPTR;
В результате получаем два новых ключевых слова: TREENODE (структура) и TREEPTR
(указатель на структуру). Тогда функ- цию TALLOC можно записать в виде
TREEPTR TALLOC()
\(
CHAR *ALLOC();
RETURN((TREEPTR) ALLOC(SIZEOF(TREENODE)));
\)
Необходимо подчеркнуть, что описание TYPEDEF не приводит к созданию нового в
каком-либо смысле типа; оно только до- бавляет новое имя для некоторого существующего
типа. при этом не возникает и никакой новой семантики: описанные таким способом
переменные обладают точно теми же свойствами, что и переменные, описанные явным
образом. По существу конструкция TYPEDEF сходна с #DEFINE за исключением того, что
она интер- претируется компилятором и потому может осуществлять подста- новки текста,
которые выходят за пределы возможностей мак- ропроцессора языка "C". Например,
TYPEDEF INT (*PFI) ();
создает тип PFI для "указателя функции, возвращающей значе- ние типа INT", который
затем можно было бы использовать в программе сортировки из главы 5 в контексте вида
PFI STRCMP, NUMCMP, SWAP;
Имеются две основные причины применения описаний TYPEDEF. Первая причина связана
с параметризацией программы, чтобы облегчить решение проблемы переносимости. Если
для ти- пов данных, которые могут быть машинно-зависимыми, использо- вать описание
TYPEDEF, то при переносе программы на другую машину придется изменить только эти
описания. Одна из типич- ных ситуаций состоит в использовании определяемых с помощью
TYPEDEF имен для различных целых величин и в последующем подходящем выборе типов
SHORT, INT и LONG для каждой имею- щейся машины. Второе назначение TYPEDEF состоит
в обеспечении лучшей доку- ментации для программы - тип с именем TREEPTR может оказать-
ся более удобным для восприятия, чем тип, который описан только как указатель сложной
структуры. И наконец, всегда существует вероятность, что в будущем ком- пилятор
или некоторая другая программа, такая как LINT, смо- жет использовать содержащуюся
в описаниях TYPEDEF информацию для проведения некоторой дополнительной проверки
программы.
* 7. Ввод и вывод *
Средства ввода/вывода не являются составной частью языка "с", так что мы не выделяли
их в нашем предыдущем изложении. Однако реальные программы взаимодействуют со своей
окружаю- щей средой гораздо более сложным образом, чем мы видели до сих пор. В этой
главе будет описана "стандартная библиотека ввода/вывода", то есть набор функций,
разработанных для обеспечения стандартной системы ввода/вывода для "с"- прог- рамм.
Эти функции предназначены для удобства программного интерфейса, и все же отражают
только те операции, которые могут быть обеспечены на большинстве современных операцион-
ных систем. Процедуры достаточно эффективны для того, чтобы пользователи редко чувствовали
необходимость обойти их "ради эффективности", как бы ни была важна конкретная задача.
И, наконец, эти процедуры задуманы быть "переносимыми" в том смысле, что они должны
существовать в совместимом виде на любой системе, где имеется язык "с", и что программы,
кото- рые ограничивают свои взаимодействия с системой возможностя- ми, предоставляемыми
стандартной библиотекой, можно будет переносить с одной системы на другую по существу
без измене- ний.
Мы здесь не будем пытаться описать всю библиотеку вво- да/вывода; мы более заинтересованы
в том, чтобы продемонст- рировать сущность написания "с"-программ, которые взаимодей-
ствуют со своей операционной средой.
7.1. Обращение к стандартной библиотеке
Каждый исходный файл, который обращается к функции из стандартной библиотеки,
должен вблизи начала содержать стро- ку
#INCLUDE <STDIO.H>
в файле STDIO.H определяются некоторые макросы и переменные, используемые библиотекой
ввода/вывода. Использование угловых скобок вместо обычных двойных кавычек - указание
компилятору искать этот файл в справочнике, содержащем заголовки стан- дартной информации
(на системе UNIX обычно LUSRLINELUDE).
Кроме того, при загрузке программы может оказаться необ- ходимым указать библиотеку
явно; на системе PDP-11 UNIX, например, команда компиляции программы имела бы вид:
CC исходные файлы и т.д. -LS
где -LS указывает на загрузку из стандартной библиотеки.
7.2. Стандартный ввод и вывод - функции 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), помещающей символ 'с'
в "стандартный ввод", который по умол- чанию является терминалом. Вывод можно направить
в некоторый файл с помощью обозначения > : если 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 преобразует букву из верхнего регист- ра в ту же букву нижнего регистра.
Независимо от того, как эти функции реализованы на конкретной машине, их внешнее
по- ведение совершенно одинаково, так что использующие их прог- раммы избавлены
от знания символьного набора.
Если требуется преобразовать несколько файлов, то можно собрать эти файлы с помощью
программы, подобной утилите CAT системы UNIX,
CAT FILE1 FILE2 ... \! LOWER>OUTPUT
и избежать тем самым вопроса о том, как обратиться к этим файлам из программы.
(Программа CAT приводится позже в этой главе).
Кроме того отметим, что в стандартной библиотеке вво- да/вывода "функции" GETCHAR
и PUTCHAR на самом деле могут быть макросами. Это позволяет избежать накладных расходов
на обращение к функции для обработки каждого символа. В главе 8 мы продемонстрируем,
как это делается.
7.3. Форматный вывод - функция PRINTF
Две функции: PRINTF для вывода и SCANF для ввода (следу- ющий раздел) позволяют
преобразовывать численные величины в символьное представлEние и обратно. Они также
позволяют ге- нерировать и интерпретировать форматные строки. Мы уже всюду в предыдущих
главах неформально использовали функцию PRINTF; здесь приводится более полное и
точное описание. Функция
PRINTF(CONTROL, ARG1, ARG2, ...)
преобразует, определяет формат и печатает свои аргументы в стандартный вывод
под управлением строки 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
значащих цифр.
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, и помещает результаты в остальные аргументы. Управляющий
аргу- мент описывается ниже; другие аргументы, каждый из которых должен быть указателем,
определяют, куда следует поместить соответствующим образом преобразованный ввод.
Управляющая строка обычно содержит спецификации преобра- зования, которые используются
для непосредственной интерпре- тации входных последовательностей. Управляющая строка
может содержать: - пробелы, табуляции или символы новой строки ("символы пус-
тых промежутков"), которые игнорируются.
- Обычные символы (не %), которые предполагаются совпадающи-
ми со следующими отличными от символов пустых промежутков
символами входного потока. - Спецификации преобразования, состоящие из символа
%, нео-
бязательного символа подавления присваивания *, необяза-
тельного числа, задающего максимальную ширину поля и сим-