Смекни!
smekni.com

па, но все же для управления таблицей самым удобным было бы,

если это значение занимало бы один и тот же объем памяти и

хранилось в том же самом месте независимо от его типа. это и

является назначением объединения - выделить отдельную пере-

менную, в которой можно законно хранить любую одну из пере-

менных нескольких типов. Как и в случае полей, синтаксис ос-

новывается на структурах.

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 &bsol;! PROG

прогоняет две программы, OTHERPROG и PROG, и организует так,

что стандартным вводом для PROG служит стандартный вывод

OTHERPROG.

Функция GETCHAR возвращает значение EOF, когда она попа-

дает на конец файла, какой бы ввод она при этом не считыва-

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

EOF равной -1 (посредством #DEFINE в файле STDIO.H), но про-

верки следует писать в терминах EOF, а не -1, чтобы избежать

зависимости от конкретного значения.

Вывод можно осуществлять с помощью функции PUTCHAR©,