Структуры особенно подходят для управления массивамисвязанных переменных. Рассмотрим, например, программу подс-чета числа вхождений каждого ключевого слова языка "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); \) Функция GETWORD использует функции GETCH и UNGETCH, которыемы написали в главе 4: когда набор алфавитных символов пре-рывается, функция GETWORD получает один лишний символ. В ре-зультате вызова UNGETCH этот символ помещается назад во вводдля следующего обращения. Функция GETWORD обращается к функции TYPE для определе-ния типа каждого отдельного символа из файла ввода. Вот ва-риант, справедливый только для алфавита ASCII. TYPE(C) /* RETURN TYPE OF ASCII CHARACTER */ INT C; \( IF (C>= 'A' && C<= 'Z' \!\! C>= 'A' && C<= 'Z') RETURN(LETTER); ELSE IF (C>= '0' && C<= '9') RETURN(DIGIT); ELSE RETURN(C); \) Символические константы LETTER и DIGIT могут иметь любыезначения, лишь бы они не вступали в конфликт с символами,отличными от буквенно-цифровых, и с EOF; очевидно возможенследующий выбор #DEFINE LETTER 'A' #DEFINE DIGIT '0' функция GETWORD могла бы работать быстрее, если бы обращенияк функции TYPE были заменены обращениями к соответствующемумассиву TYPE[ ]. В стандартной библиотеке языка "C" предус-мотрены макросы ISALPHA и ISDIGIT, действующие необходимымобразом. Упражнение 6-1 -------------- Сделайте такую модификацию функции GETWORD и оцените,как изменится скорость работы программы. Упражнение 6-2 -------------- Напишите вариант функции TYPE, не зависящий от конкрет-ного наборасимволов. Упражнение 6-3 -------------- Напишите вариант программы подсчета ключевых слов, кото-рый бы не учитывал появления этих слов в заключенных в ка-вычки строках.