- •Передмова
- •Передмова до першого видання
- •Вступ
- •Вступний урок
- •Твердження for
- •Масиви
- •Аргументи - виклик за значенням
- •Типи, оператори та вирази
- •Константи
- •Оголошення
- •Оператори приросту та спаду
- •Оператори та вирази присвоєння
- •Вирази умов
- •Керування потоком
- •Вирази та блоки
- •If-else
- •Else if
- •Switch
- •Цикли while та for
- •Файли заголовка
- •Препроцесор C
- •Покажчики та масиви
- •Покажчики й адреси
- •Покажчики та масиви
- •Арифметика адрес
- •Аргументи командного рядка
- •Структури
- •Масиви структур
- •Покажчики на структури
- •Typedef
- •Сполуки
- •Ungetc
- •Виконання команд
- •Генератор випадкових чисел
- •Open, creat, close, unlink
6.3. МАСИВИ СТРУКТУР |
151 |
struct { int len;
char *str; } *p;
то
++p->len
здiйснює прирiст len, а не p, оскiльки неявнi дужки виглядають як ++(p->len). Дужки можна використати, щоб змiнити спрягання (зв’язування): (++p)->len збiльшує p перед тим як дiстатися до len, тодi як (p++)->len збiльшує p пiсля. (Цей останнiй набiр дужок необов’язковий.) Подiбно до цього, *p->str добуває значення, на яке вказує str; *p->str++ здiйснює прирiст str пiсля доступу до того, на що вона вказує (схоже до *s++); (*p->str)++ збiльшує те, на що вказує str; a *p++->str збiльшує p пiсля доступу до того, на що вказує str.
6.3Масиви структур
Уявiмо собi, що нам треба написати програму, яка би рахувала кiлькiсть знайдених ключових слiв C. Нам потрiбен би був масив символьних ланцюжкiв, який би мiстив назви, i масив цiлих для вiдлiку. Одним з можливих варiантiв було би використання двох паралельних масивiв keyword i keycount, як от
char *keyword[NKEYS]; int keycount[NKEYS];
Але самий факт того, що масиви паралельнi, пiдказує вiдмiнну органiзацiю — масив структур. Кожне ключове слово складатиметься з пари:
char *word; int cout;
i цi пари утворюватимуть масив. Оголошення структури
struct key { char *word; int count;
} keytab[NKEYS];
152 |
РОЗДIЛ 6. СТРУКТУРИ |
заявляє про структуру типу key, i означує масив keytab, що мiститиме структури цього типу, вiдводячи для них мiсце в пам’ятi. Кожен елемент масиву буде структурою. Це можна також написати як
struct key { char *word; int count;
};
struct key keytab[NKEYS];
Оскiльки структура keytab мiстить сталий набiр iмен, найлегшим буде зробити її зовнiшньою змiнною й iнiцiювати раз i назавжди пiд час її означення. Iнiцiалiзацiя структури аналогiчна попереднiм — за визначенням слiдує список iнiцiалiзаторiв, включених у фiгурнi дужки:
struct key { char *word; int count;
} keytab[] = { "auto", 0, "break", 0, "case", 0, "char", 0, "const", 0, "continue", 0, "default", 0, /* ... */ "unsigned", 0, "void", 0, "volatile", 0, "while", 0
};
Iнiцiалiзатори перелiчено парами, згiдно елементiв структури. Було би точнiше включити iнiцiалiзатори кожного рядка структури у фiгурнi дужки, як от
{"auto", 0 },
{"break", 0 },
{"case", 0 },
...
6.3. МАСИВИ СТРУКТУР |
153 |
але внутрiшнi дужки — необов’язковi, якщо iнiцiалiзатори складаються з простих змiнних або символьних ланцюжкiв, i коли всi присутнi. Зазвичай, кiлькiсть елементiв масиву keytab обчислюється автоматично, при наявностi iнiцiалiзаторiв i порожньому []. Програма вiдлiку ключових слiв почнеться з означення keytab. Функцiя main читатиме ввiд шляхом повторного виклику getword, яка добуватиме по одному слову за раз. Кожне слово шукатиметься в keytab за допомогою нашої версiї функцiї бiнарного пошуку, написаної в Роздiлi 3. Список ключових слiв потрiбно буде сортувати в послiдовностi зростання в таблицi.
#include <stdio.h> #include <ctype.h> #include <string.h>
#define MAXWORD 100
int getword(char *, int);
int binsearch(char *, struct key *, int);
/* вiдлiк ключових слiв C */ main()
{
int n;
char word[MAXWORD];
while (getword(word, MAXWORD) != EOF) if (isalpha(word[0]))
if ((n = binsearch(word, keytab, NKEYS)) >= 0) keytab[n].count++;
for (n = 0; n < NKEYS; n++) if (keytab[n].count > 0)
printf("%4d %s\n",
keytab[n].count, keytab[n].word);
return 0;
}
/* binsearch: знаходить слово word у tab[0]...tab[n-1] */ int binsearch(char *word, struct key tab[], int n)
{
int cond;
int low, high, mid;
low = 0; high = n - 1;
154 РОЗДIЛ 6. СТРУКТУРИ
while (low <= high) { mid = (low+high) / 2;
if ((cond = strcmp(word, tab[mid].word)) < 0) high = mid - 1;
else if (cond > 0) low = mid + 1;
else
return mid;
}
return -1;
}
Ми покажемо функцiю getword за якусь мить; поки-що досить знати, що кожний її виклик знаходить нове слово, яке скопiйовано в масив, вказаний як її перший аргумент. Кiлькiсть NKEYS є числом ключових слiв у keytab. Хоча ми могли би самi порахувати це число, набагато легше i безпечнiше залишити це машинi, особливо якщо список може змiнитися пiзнiше. Однiєю з можливостей буде завершити список iнiцiалiзаторiв нульовим покажчиком, пiсля чого iтерувати через keytab до досягнення кiнця.
Але це вкрай потрiбно, оскiльки розмiр масиву повнiстю визначається пiд час компiляцiї. Розмiр масиву складає розмiр одного елемента помножено на кiлькiсть елементiв, тож кiлькiсть складатиме просто
розмiр keytab / розмiр struct key
C забезпечує унарним компiляцiйним оператором пiд назвою sizeof, який можна застосувати для визначення розмiру будь-якого об’єкту. Вирази
sizeof об’єкт
i
sizeof (назва типу)
повертають цiле число, рiвне розмiровi в байтах вказаного об’єкту або типу. (Точнiше, sizeof повертає беззнакове цiле значення, чий тип size_t визначено в файлi заголовку <stddef.h>.) Об’єктом може служити як змiнна, так i масив або структура. Назва типу також може бути однiєю з назв основних типiв, таких як int або doble, або ж назвою похiдного типу, як структура або покажчик. В нашому випадку, кiлькiсть ключових слiв дорiвнюватиме розмiровi масиву, подiлено на розмiр одного елемента. Це обчислення використано в твердженнi #define для того, щоб встановити значення
NKEYS:
#define NKEYS (sizeof keytab / sizeof(struct key))
6.3. МАСИВИ СТРУКТУР |
155 |
Iншим шляхом буде дiлення масиву на розмiр певного елемента:
#define NKEYS (sizeof keytab / sizeof(keytab[0]))
Останнє має перевагу в тому, що його не потрiбно мiняти, якщо тип змiнено. sizeof неможливо використати в рядку #if, оскiльки препроцесор не розумiє назв типiв. Зате вираз у #define не обчислюється препроцесором, тож цей код чинний.
Тепер, щодо функцiї getword. Ми написали бiльш узагальнену getword, нiж ця програма потребує, але вона не є складною. getword добуває наступне слово зi вводу, де словом може служити або ланцюжок з лiтер i цифр, який починається з лiтери, або один символ, яке не є пробiлом. Функцiя повертає перший знак слова, або EOF у випадку кiнця файла, або самий знак, якщо вiн не є частиною алфавiту.
/* getword: одержує наступне слово зi вводу */ int getword(char *word, int lim)
{
int c, getch(void); void ungetch(int); char *w = word;
while (isspace(c = getch()))
;
if (c != EOF) *w++ = c;
if (!isalpha(c)) { *w = ’\0’; return c;
}
for ( ; --lim > 0; w++)
if (!isalnum(*w = getch())) { ungetch(*w);
break;
}
*w = ’\0’; return word[0];
}
getword використовує getch i ungetch, якi ми написали в Роздiлi 4. Коли набiр буквенно-цифрових лексем закiнчився, це означає, що getword зайшла на один символ задалеко. Виклик ungetch проштовхує символ назад на ввiд для наступного виклику
