па, но все же для управления таблицей самым удобным было бы,
если это значение занимало бы один и тот же объем памяти и
хранилось в том же самом месте независимо от его типа. это и
является назначением объединения - выделить отдельную пере-
менную, в которой можно законно хранить любую одну из пере-
менных нескольких типов. Как и в случае полей, синтаксис ос-
новывается на структурах.
UNION U_TAG \(
INT IVAL;
FLOAT FVAL;
CHAR *PVAL;
\) UVAL;
Переменная UVAL будет иметь достаточно большой размер,чтобы
хранить наибольший из трех типов, независимо от машины, на
которой осуществляется компиляция, - программа не будет за-
висить от характеристик аппаратных средств. Любой из этих
трех типов может быть присвоен UVAR и затем использован в
выражениях, пока такое использование совместимо: извлекаемый
тип должен совпадать с последним помещенным типом. Дело
программиста - следить за тем, какой тип хранится в объеди-
нении в данный момент; если что-либо хранится как один тип,
а извлекается как другой, то результаты будут зависеть от
используемой машины.
· 149 -
Синтаксически доступ к членам объединения осуществляется
следующим образом:
имя объединения.член
или
указатель объединения ->член
то есть точно так же, как и в случае структур. если для отс-
леживания типа, хранимого в данный момент в UVAL, использу-
ется переменная UTYPE, то можно встретить такой участок
программы:
IF (UTYPE == INT)
PRINTF(“%D\N”, UVAL.IVAL);
ELSE IF (UTYPE == FLOAT)
PRINTF(“%F\N”, UVAL.FVAL);
ELSE IF (UTYPE == STRING)
PRINTF(“%S\N”, UVAL.PVAL);
ELSE
PRINTF(“BAD TYPE %D IN UTYPE\N”, UTYPE);
Объединения могут появляться внутри структур и массивов
и наоборот. Запись для обращения к члену объединения в
структуре (или наоборот) совершенно идентична той, которая
используется во вложенных структурах. например, в массиве
структур, определенным следующим образом
STRUCT \(
CHAR *NAME;
INT FLAGS;
INT UTYPE;
UNION \(
INT IVAL;
FLOAT FVAL;
CHAR *PVAL;
\) UVAL;
\) SYMTAB[NSYM];
на переменную IVAL можно сослаться как
SYMTAB[I].UVAL.IVAL
а на первый символ строки PVAL как
*SYMTAB[I].UVAL.PVAL
В сущности объединение является структурой, в которой все
члены имеют нулевое смещение. Сама структура достаточно ве-
лика, чтобы хранить “самый широкий” член, и выравнивание
пригодно для всех типов, входящих в объединение. Как и в
случае структур, единственными операциями, которые в настоя-
щее время можно проводить с объединениями, являются доступ к
· 150 -
члену и извлечение адреса; объединения не могут быть присво-
ены, переданы функциям или возвращены ими. указатели объеди-
нений можно использовать в точно такой же манере, как и ука-
затели структур.
Программа распределения памяти, приводимая в главе 8 ,
показывает, как можно использовать объединение, чтобы сде-
лать некоторую переменную выровненной по определенному виду
границы памяти.
6.9. Определение типа
В языке “C” предусмотрена возможность, называемая TYPEDEF
для введения новых имен для типов данных. Например, описание
TYPEDEF INT LENGTH;
делает имя LENGTH синонимом для INT. “Тип” LENGTH может быть использован в описаниях, переводов типов и т.д. Точно таким же образом, как и тип INT:
LENGTH LEN, MAXLEN;
LENGTH *LENGTHS[];
Аналогично описанию
TYPEDEF CHAR *STRING;
делает STRING синонимом для CHAR*, то есть для указателя на
символы, что затем можно использовать в описаниях вида
STRING P, LINEPTR[LINES], ALLOC();
Обратите внимание, что объявляемый в конструкции TYPEDEF
тип появляется в позиции имени переменной, а не сразу за
словом TYPEDEF. Синтаксически конструкция TYPEDEF подобна
описаниям класса памяти EXTERN, STATIC и т. Д. мы также ис-
пользовали прописные буквы, чтобы яснее выделить имена.
В качестве более сложного примера мы используем конст-
рукцию TYPEDEF для описания узлов дерева, рассмотренных ра-
нее в этой главе:
TYPEDEF STRUCT TNODE \( /* THE BASIC NODE */
CHAR WORD; / POINTS TO THE TEXT */
INT COUNT; /* NUMBER OF OCCURRENCES */
STRUCT TNODE LEFT; / LEFT CHILD */
STRUCT TNODE RIGHT; / RIGHT CHILD */
\) TREENODE, *TREEPTR;
В результате получаем два новых ключевых слова: TREENODE
(структура) и TREEPTR (указатель на структуру). Тогда функ-
цию TALLOC можно записать в виде
· 151 -
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 информацию
для проведения некоторой дополнительной проверки программы.
· 152 -
7. Ввод и вывод
Средства ввода/вывода не являются составной частью языка
“с”, так что мы не выделяли их в нашем предыдущем изложении.
Однако реальные программы взаимодействуют со своей окружаю-
щей средой гораздо более сложным образом, чем мы видели до
сих пор. В этой главе будет описана “стандартная библиотека
ввода/вывода”, то есть набор функций, разработанных для
обеспечения стандартной системы ввода/вывода для “с”- прог-
рамм. Эти функции предназначены для удобства программного
интерфейса, и все же отражают только те операции, которые
могут быть обеспечены на большинстве современных операцион-
ных систем. Процедуры достаточно эффективны для того, чтобы
пользователи редко чувствовали необходимость обойти их “ради
эффективности”, как бы ни была важна конкретная задача. И,
наконец, эти процедуры задуманы быть “переносимыми” в том
смысле, что они должны существовать в совместимом виде на
любой системе, где имеется язык “с”, и что программы, кото-
рые ограничивают свои взаимодействия с системой возможностя-
ми, предоставляемыми стандартной библиотекой, можно будет
переносить с одной системы на другую по существу без измене-
ний.
Мы здесь не будем пытаться описать всю библиотеку вво-
да/вывода; мы более заинтересованы в том, чтобы продемонст-
рировать сущность написания “с”-программ, которые взаимодей-
ствуют со своей операционной средой.
7.1. Обращение к стандартной библиотеке
Каждый исходный файл, который обращается к функции из
стандартной библиотеки, должен вблизи начала содержать стро-
ку
#INCLUDE <STDIO.H>
в файле STDIO.H определяются некоторые макросы и переменные,
используемые библиотекой ввода/вывода. Использование угловых
скобок вместо обычных двойных кавычек - указание компилятору
искать этот файл в справочнике, содержащем заголовки стан-
дартной информации (на системе UNIX обычно LUSRLINELUDE).
Кроме того, при загрузке программы может оказаться необ-
ходимым указать библиотеку явно; на системе PDP-11 UNIX,
например, команда компиляции программы имела бы вид:
CC исходные файлы и т.д. -LS
где -LS указывает на загрузку из стандартной библиотеки.
7.2. Стандартный ввод и вывод - функции GETCHAR и
PUTCHAR
Самый простой механизм ввода заключается в чтении по од-
ному символу за раз из “стандартного ввода”, обычно с терми-
нала пользователя, с помощью функции GETCHAR. Функция
GETCHAR() при каждом к ней обращении возвращает следующий
· 153 -
вводимый символ. В большинстве сред, которые поддерживают
язык “с”, терминал может быть заменен некоторым файлом с по-
мощью обозначения < : если некоторая программа 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©,