6.2. Структуры и функции.
В языке “C” существует ряд ограничений на использование
структур. Обязательные правила заключаются в том, что единс-
твенные операции, которые вы можете проводить со структура-
ми, состоят в определении ее адреса с помощью операции & и
доступе к одному из ее членов. Это влечет за собой то, что
структуры нельзя присваивать или копировать как целое, и что
они не могут быть переданы функциям или возвращены ими. (В
последующих версиях эти ограничения будут сняты). На указа-
тели структур эти ограничения однако не накладываются, так
что структуры и функции все же могут с удобством работать
совместно. И наконец, автоматические структуры, как и авто-
матические массивы, не могут быть инициализированы; инициа-
лизация возможна только в случае внешних или статических
структур.
Давайте разберем некоторые из этих вопросов, переписав с
этой целью функции перобразования даты из предыдущей главы
так, чтобы они использовали структуры. Так как правила зап-
рещают непосредственную передачу структуры функции, то мы
должны либо передавать отдельно компоненты, либо передать
указатель всей структуры. Первая возможность демонстрируется
на примере функции DAY_OF_YEAR, как мы ее написали в главе
5:
D.YEARDAY = DAY_OF_YEAR(D.YEAR, D.MONTH, D.DAY);
·
131 -
другой способ состоит в передаче указателя. если мы опишем
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 необходимы, потому что операция указания члена
· 132 -
стуктуры старше , чем * . Обе операции, “->” и “.”, ассоции-
руются слева направо, так что конструкции слева и справа
зквивалентны
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.
· 133 -
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 \),
. . .
· 134 -
Но когда инициализаторы являются простыми переменными или
символьными строками и все они присутствуют, то во внутрен-
них фигурных скобках нет необходимости. Как обычно, компиля-
тор сам вычислит число элементов массива 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 каждый раз, как она находит слово,
и копирует это слово в свой первый аргумент.
·
135 -
Величина 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©;
\)
· 136 -
WHILE (--LIM > 0) \(
T = TYPE(C = *W++ = GETCH());
IF (T ! = LETTER && T ! = DIGIT) \(
UNGETCH©;
BREAK;
\)
\)
*(W-1) - '\0';
RETURN(LETTER);
\)
Функция GETWORD использует функции GETCH и UNGETCH, которые
мы написали в главе 4: когда набор алфавитных символов пре-
рывается, функция GETWORD получает один лишний символ. В ре-
зультате вызова UNGETCH этот символ помещается назад во ввод
для следующего обращения.
Функция GETWORD обращается к функции TYPE для определе-
ния типа каждого отдельного символа из файла ввода. Вот ва-
риант, справедливый только для алфавита ASCII.
TYPE© /* 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©;
\)
Символические константы LETTER и DIGIT могут иметь любые
значения, лишь бы они не вступали в конфликт с символами,
отличными от буквенно-цифровых, и с EOF; очевидно возможен
следующий выбор
#DEFINE LETTER 'A'
#DEFINE DIGIT '0'
функция GETWORD могла бы работать быстрее, если бы обращения
к функции TYPE были заменены обращениями к соответствующему
массиву TYPE[ ]. В стандартной библиотеке языка “C” предус-
мотрены макросы ISALPHA и ISDIGIT, действующие необходимым
образом.
Упражнение 6-1.
Сделайте такую модификацию функции GETWORD и оцените,
как изменится скорость работы программы.
Упражнение 6-2.
Напишите вариант функции TYPE, не зависящий от конкрет-
ного наборасимволов.
·
137 -
Упражнение 6-3.
Напишите вариант программы подсчета ключевых слов, кото-
рый бы не учитывал появления этих слов в заключенных в ка-
вычки строках.
... основаниям. При этом философская абстракция языка оказывается неразрывно связана с основными темами и движениями философии в целом. Более конкретно, на ранние стадии традиционно рассматриваемого в рамках АФ анализа обыденного языка глубокое влияние оказала философия Дж. Э. Мура, особенно его учение о здравом смысле, согласно которому такие понятия, как «человек», «мир», «я», «внешний мир», « ...
... и других странах СНГ, а также облегчение доступа к русской и мировой культуре и науке. Таким образом, судя по данным наших исследований, востребованность русского языка осталась в республике достаточно высокой. Многие представители современной молдавской молодежи продолжают, как их отцы и деды, тянуться к русской культуре, научным и техническим достижениям России. Русский язык остается языком ...
... рисуночное словесно-слоговое письмо). Памятники среднеэламского периода (14—12 вв. до н.э.) выполнены аккадской клинописью. Памятники новоэламского периода относятся к 8—6 вв. до н.э. Был официальным языком в персидском государстве Ахеменидов в 6—4 вв. предполагается, что он, подвергшись влиянию древнеперсидского, сохранился до раннего средневековья. 7. Бурушаски язык Язык бурушаски ( ...
... /диалект), скифский, согдийский, среднеперсидский, таджикский, таджриши (язык/диалект), талышский, татский, хорезмийский, хотаносакский, шугнано-рушанская группа языков, ягнобский, язгулямский и др. Они относятся к индоиранской ветви индоевропейских языков. Области распространения: Иран, Афганистан, Таджикистан, некоторые районы Ирака, Турции, Пакистана, Индии, Грузии, Российской Федерации. Общее ...
0 комментариев