RuLibrary.com

ГЛАВНАЯ | ПОИСК | ТОП | КАРТА САЙТА      

 
 


 

Керниган Ричи >> Язык C (страница 18)


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

6.1. Основные сведения

Давайте снова обратимся к процедурам преобразования даты из главы 5. Дата состоит из нескольких частей таких, как день, месяц, и год, и, возможно, день года и имя месяца. Эти пять переменных можно объеденить в одну структуру вида:

STRUCT DATE \(

INT DAY;

INT MONTH;

INT YEAR;

INT YEARDAY;

CHAR MON_NAME[4];

\);

Описание структуры, состоящее из заключенного в фигурные скобки списка описаний, начинается с ключевого слова STRUCT. За словом STRUCT может следовать необязательное имя, называ- емое ярлыком структуры (здесь это DATе). Такой ярлык именует структуры этого вида и может использоваться в дальнейшем как сокращенная запись подробного описания.

Элементы или переменные, упомянутые в структуре, называ- ются членами. Ярлыки и члены структур могут иметь такие же имена, что и обычные переменные (т.е. Не являющиеся членами структур), поскольку их имена всегда можно различить по кон- тексту. Конечно, обычно одинаковые имена присваивают только тесно связанным объектам.

Точно так же, как в случае любого другого базисного ти- па, за правой фигурной скобкой, закрывающей список членов, может следовать список переменных. Оператор

STRUCT \( ...\) X,Y,Z;

синтаксически аналогичен

INT X,Y,Z;

в том смысле, что каждый из операторов описывает X , Y и Z в качестве переменных соотвествующих типов и приводит к выде- лению для них памяти.

Описание структуры, за которым не следует списка пере- менных, не приводит к выделению какой-либо памяти; оно толь- ко определяет шаблон или форму структуры. Однако, если такое описание снабжено ярлыком, то этот ярлык может быть исполь- зован позднее при определении фактических экземпляров струк- тур. Например, если дано приведенное выше описание DATE, то

STRUCT DATE D;

определяет переменную D в качестве структуры типа DATE. Внешнюю или статическую структуру можно инициализировать, поместив вслед за ее определением список инициализаторов для ее компонент:

STRUCT DATE D=\( 4, 7, 1776, 186, "JUL"\);

Член определенной структуры может быть указан в выраже- нии с помощью конструкции вида

имя структуры . Член

Операция указания члена структуры "." связывает имя структу- ры и имя члена. В качестве примера определим LEAP (признак високосности года) на основе даты, находящейся в структуре D,

LEAP = D.YEAR % 4 == 0 && D.YEAR % 100 != 0

\!\! D.YEAR % 400 == 0;

или проверим имя месяца

IF (STRCMP(D.MON_NAME, "AUG") == 0) ...

Или преобразуем первый символ имени месяца так, чтобы оно начиналось со строчной буквы

D.MON_NAME[0] = LOWER(D.MON_NAME[0]);

Структуры могут быть вложенными; учетная карточка служа- щего может фактически выглядеть так:

STRUCT PERSON \(

CHAR NAME[NAMESIZE];

CHAR ADDRESS[ADRSIZE];

LONG ZIPCODE; /* почтовый индекс */

LONG SS_NUMBER; /* код соц. Обеспечения */

DOUBLE SALARY; /* зарплата */

STRUCT DATE BIRTHDATE; /* дата рождения */

STRUCT DATE HIREDATE; /* дата поступления

на работу */

\);

Структура PERSON содержит две структуры типа DATE . Если мы определим EMP как

STRUCT PERSON EMP;

то

EMP.BIRTHDATE.MONTH

будет ссылаться на месяц рождения. Операция указания члена структуры "." ассоциируется слева направо.

6.2. Структуры и функции

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

Давайте разберем некоторые из этих вопросов, переписав с этой целью функции перобразования даты из предыдущей главы так, чтобы они использовали структуры. Так как правила зап- рещают непосредственную передачу структуры функции, то мы должны либо передавать отдельно компоненты, либо передать указатель всей структуры. Первая возможность демонстрируется на примере функции DAY_OF_YEAR, как мы ее написали в главе 5:

D.YEARDAY = DAY_OF_YEAR(D.YEAR, D.MONTH, D.DAY);

другой способ состоит в передаче указателя. если мы опишем HIREDATE как

STRUCT DATE HIREDATE;

и перепишем DAY_OF_YEAR нужным образом, мы сможем тогда на- писать

HIREDATE YEARDAY = DAY_OF_YEAR(&HIREDATE);

передавая указатель на HIREDATE функции DAY_OF_YEAR . Функ- ция должна быть модифицирована, потому что ее аргумент те- перь является указателем, а не списком переменных.

DAY_OF_YEAR(PD) /* SET DAY OF YEAR FROM MONTH, DAY */

STRUCT DATE *PD;

\( INT I, DAY, LEAP;

DAY = PD->DAY; LEAP = PD->YEAR % 4 == 0 && PD->YEAR % 100 != 0

\!\! PD->YEAR % 400 == 0; FOR (I =1; I < PD->MONTH; I++)

DAY += DAY_TAB[LEAP][I]; RETURN(DAY);

\)

Описание

STRUCT DATE *PD;

говорит, что PD является указателем структуры типа DATE. Запись, показанная на примере

PD->YEAR

является новой. Если P - указатель на структуру, то

P-> член структуры

обращается к конкретному члену. (Операция -> - это знак ми- нус, за которым следует знак ">".)

Так как PD указывает на структуру, то к члену YEAR можно обратиться и следующим образом

(*PD).YEAR

но указатели структур используются настолько часто, что за- пись -> оказывается удобным сокращением. Круглые скобки в (*PD).YEAR необходимы, потому что операция указания члена

стуктуры старше , чем * . Обе операции, "->" и ".", ассоции- руются слева направо, так что конструкции слева и справа зквивалентны

P->Q->MEMB (P->Q)->MEMB

EMP.BIRTHDATE.MONTH (EMP.BIRTHDATE).MONTH

Для полноты ниже приводится другая функция, MONTH_DAY, пере- писанная с использованием структур.

MONTH_DAY(PD) /* SET MONTH AND DAY FROM DAY OF YEAR */

STRUCT DATE *PD;

\(

INT I, LEAP;

LEAP = PD->YEAR % 4 == 0 && PD->YEAR % 100 != 0

\!\! PD->YEAR % 400 == 0;

PD->DAY = PD->YEARDAY;

FOR (I = 1; PD->DAY > DAY_TAB[LEAP][I]; I++)

PD->DAY -= DAY_TAB[LEAP][I];

PD->MONTH = I;

\)

Операции работы со структурами "->" и "." наряду со () для списка аргументов и [] для индексов находятся на самом верху иерархии страшинства операций и, следовательно, связы- ваются очень крепко. Если, например, имеется описание

STRUCT \(

INT X;

INT *Y;

\) *P;

то выражение

++P->X

увеличивает х, а не р, так как оно эквивалентно выражению ++(P->х). Для изменения порядка выполнения операций можно использовать круглые скобки: (++P)->х увеличивает P до дос- тупа к х, а (P++)->X увеличивает P после. (круглые скобки в последнем случае необязательны. Почему ?)

Совершенно аналогично *P->Y извлекает то, на что указы- вает Y; *P->Y++ увеличивает Y после обработки того, на что он указывает (точно так же, как и *S++); (*P->Y)++ увеличи- вает то, на что указывает Y; *P++->Y увеличивает P после вы- борки того, на что указывает Y.

6.3. Массивы сруктур

Структуры особенно подходят для управления массивами связанных переменных. Рассмотрим, например, программу подс- чета числа вхождений каждого ключевого слова языка "C". Нам нужен массив символьных строк для хранения имен и массив це- лых для подсчета. одна из возможностей состоит в использова- нии двух параллельных массивов KEYWORD и KEYCOUNT:

CHAR *KEYWORD [NKEYS]; INT KEYCOUNT [NKEYS];

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

CHAR *KEYWORD; INT KEYCOUNT;

и, следовательно, имеется массив пар. Описание структуры

STRUCT KEY \(

CHAR *KEYWORD;

INT KEYCOUNT; \) KEYTAB [NKEYS];

оперделяет массив KEYTAB структур такого типа и отводит для них память. Каждый элемент массива является структурой. Это можно было бы записать и так:

STRUCT KEY \(

CHAR *KEYWORD;

INT KEYCOUNT; \); STRUCT KEY KEYTAB [NKEYS];

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

STRUCT KEY \(

CHAR *KEYWORD;

INT KEYCOUNT;

\) KEYTAB[] =\(

"BREAK", 0,

"CASE", 0,

"CHAR", 0,

"CONTINUE", 0,

"DEFAULT", 0,

/* ... */

"UNSIGNED", 0,

"WHILE", 0

\);

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

\( "BREAK", 0 \),

\( "CASE", 0 \),

. . .

Но когда инициализаторы являются простыми переменными или символьными строками и все они присутствуют, то во внутрен- них фигурных скобках нет необходимости. Как обычно, компиля- тор сам вычислит число элементов массива KEYTAB, если иници- ализаторы присутствуют, а скобки [] оставлены пустыми.

Программа подсчета ключевых слов начинается с определе- ния массива KEYTAB. ведущая программа читает свой файл вво- да, последовательно обращаясь к функции GETWORD, которая из- влекает из ввода по одному слову за обращение. Каждое слово ищется в массиве KEYTAB с помощью варианта функции бинарного поиска, написанной нами в главе 3. (Конечно, чтобы эта функ- ция работала, список ключевых слов должен быть расположен в порядке возрастания).

#DEFINE MAXWORD 20

MAIN() /* COUNT "C" KEYWORDS */

\(

INT N, T;

CHAR WORD[MAXWORD];

WHILE ((T = GETWORD(WORD,MAXWORD)) != EOF)

IF (T == LETTER)

IF((N = BINARY(WORD,KEYTAB,NKEYS)) >= 0)

KEYTAB[N].KEYCOUNT++;

FOR (N =0; N < NKEYS; N++)

IF (KEYTAB[N].KEYCOUNT > 0)

PRINTF("%4D %S\N",

KEYTAB[N].KEYCOUNT, KEYTAB[N].KEYWORD);

\)

BINARY(WORD, TAB, N) /* FIND WORD IN TAB[0]...TAB[N-1] */

CHAR *WORD;

STRUCT KEY TAB[];

INT N;

\(

INT LOW, HIGH, MID, COND;

LOW = 0;

HIGH = N - 1;

WHILE (LOW <= HIGH) \(

MID = (LOW+HIGH) / 2;

IF((COND = STRCMP(WORD, TAB[MID].KEYWORD)) < 0)

HIGH = MID - 1;

ELSE IF (COND > 0)

LOW = MID + 1;

ELSE

RETURN (MID);

\)

RETURN(-1);

\) Мы вскоре приведем функцию GETWORD; пока достаточно сказать, что она возвращает LETTER каждый раз, как она находит слово, и копирует это слово в свой первый аргумент.

Величина NKEYS - это количество ключевых слов в массиве KEYTAB . Хотя мы можем сосчитать это число вручную, гораздо легче и надежнее поручить это машине, особенно в том случае, если список ключевых слов подвержен изменениям. Одной из возможностей было бы закончить список инициализаторов указа- нием на нуль и затем пройти в цикле сквозь массив KEYTAB, пока не найдется конец.

Но, поскольку размер этого массива полностью определен к моменту компиляции, здесь имеется более простая возможность. Число элементов просто есть

SIZE OF KEYTAB / SIZE OF STRUCT KEY

дело в том, что в языке "C" предусмотрена унарная операция SIZEOF, выполняемая во время компиляции, которая позволяет вычислить размер любого объекта. Выражение

SIZEOF(OBJECT)

выдает целое, равное размеру указанного объекта. (Размер оп- ределяется в неспецифицированных единицах, называемых "бай- тами", которые имеют тот же размер, что и переменные типа CHAR). Объект может быть фактической переменной, массивом и структурой, или именем основного типа, как INT или DOUBLE, или именем производного типа, как структура. В нашем случае число ключевых слов равно размеру массива, деленному на раз- мер одного элемента массива. Это вычисление используется в утверждении #DEFINE для установления значения NKEYS:

#DEFINE NKEYS (SIZEOF(KEYTAB) / SIZEOF(STRUCT KEY))

Теперь перейдем к функции GETWORD. Мы фактически написа- ли более общий вариант функции GETWORD, чем необходимо для этой программы, но он не на много более сложен. Функция GETWORD возвращает следующее "слово" из ввода, где словом считается либо строка букв и цифр, начинающихся с буквы, ли- бо отдельный символ. Тип объекта возвращается в качетве зна- чения функции; это - LETTER, если найдено слово, EOF для конца файла и сам символ, если он не буквенный.

GETWORD(W, LIM) /* GET NEXT WORD FROM INPUT */

CHAR *W;

INT LIM;

\(

INT C, T;

IF (TYPE(C=*W++=GETCH()) !=LETTER) \(

*W='\0';

RETURN(C);

\)

WHILE (--LIM > 0) \(

T = TYPE(C = *W++ = GETCH());

IF (T ! = LETTER && T ! = DIGIT) \(

UNGETCH(C);

BREAK;

\)

\)

*(W-1) - '\0';

RETURN(LETTER);

Название книги: Язык C
Автор: Керниган Ричи
Просмотрено 45090 раз

123456789101112131415161718192021222324252627282930313233


 
Page generation 0.003 seconds