
- •1.1 Перші кроки
- •1.2 Змінні й арифметичні вирази
- •1.3 Твердження for
- •1.4 Символічні константи
- •1.5 Ввід і вивід знаків
- •1.5.1 Копіювання файла
- •1.5.2 Відлік символів
- •1.5.3 Відлік рядків
- •1.6 Масиви
- •1.7 Функції
- •1.8 Аргументи - виклик за значенням
- •1.9 Символьні масиви
- •1.10 Зовнішні змінні й область дії
- •2.1 Назви змінних
- •2.2 Типи даних і розміри
- •2.3 Константи
- •2.4 Оголошення
- •2.5 Арифметичні операції
- •2.6 Реляційні та логічні оператори
- •2.7 Перетворення типів
- •2.8 Оператори приросту та спаду
- •2.9 Розрядні оператори
- •2.10 Оператори та вирази присвоєння
- •2.11 Вирази умов
- •2.12 Пріоритет і послідовність обчислення
- •3.1 Вирази та блоки
- •3.3 Else if
- •3.4 Switch
- •3.5 Цикли while та for
- •3.6 Цикли do-while
- •3.7 Break і continue
- •3.8 Goto та мітки
- •4.1 Основні знання про функції
- •4.2 Функції, які не повертають цілих
- •4.3 Зовнішні змінні
- •4.4 Правила області дії
- •4.5 Файли заголовка
- •4.6 Статичні змінні
- •4.7 Регістрові змінні
- •4.8 Структура блоків
- •4.10 Рекурсія
- •4.11 Препроцесор c
- •4.11.1 Включення файлів
- •4.11.2 Заміна макросів
- •4.11.3 Обумовлене включення файлів
- •5.1 Покажчики й адреси
- •5.2 Покажчики й аргументи функцій
- •5.3 Покажчики та масиви
- •5.4 Арифметика адрес
- •5.5 Покажчики на символи та функції
- •5.6 Масив покажчиків; покажчики на покажчики
- •5.7 Багатовимірні масиви
- •5.8 Ініціалізація масиву покажчиків
- •5.9 Покажчики в порівнянні з багатовимірними масивами
- •5.10 Аргументи командного рядка
- •5.11 Покажчики на функції
- •5.12 Складні оголошення
- •6.1 Основні поняття про структури
- •6.2 Структури та функції
- •6.3 Масиви структур
- •6.4 Покажчики на структури
- •6.5 Структури зі зворотнім звертанням
- •6.6 Пошук по таблиці
- •6.7 Typedef
- •6.8 Сполуки
- •6.9 Розрядні поля
- •7.1 Стандартний ввід і вивід
- •7.2 Форматований вивід - printf
- •7.3 Списки аргументів довільної довжини
- •7.4 Форматований ввід - scanf
- •7.5 Доступ до файлів
- •7.6 Обробка помилок - stderr і exit
- •7.7 Ввід і вивід рядків
- •7.8 Додаткові функції
- •7.8.1 Операції з ланцюжками
- •7.8.2 Перевірка і перетворення класів символів
- •7.8.3 Ungetc
- •7.8.4 Виконання команд
- •7.8.5 Керування пам'яттю
- •7.8.6 Математичні функції
- •7.8.7 Генератор випадкових чисел
- •8.1 Дескриптори файлів
- •8.2 Низькорівневий ввід/вивід - read і write
- •8.3 Open, creat, close, unlink
- •8.4 Довільний доступ - lseek
- •8.5 Приклад: втілення fopen і getc
- •8.6 Приклад - перелік вмісту каталогів
- •8.7 Приклад - розподільник пам'яті
6.6 Пошук по таблиці
У цьому розділі, ми напишемо осердя програми пошуку за таблицею, щоб проілюструвати додаткові риси структур. Цей код типовий для того, що можна знайти у функціях по обробці таблиці символів макро-процесору або компілятора. Наприклад, розглянемо твердження#define. Коли ми зустріли рядок на кшталт
#define IN 1
назва IN і її текст заміни 1 буде збережено в таблиці. Пізніше, коли IN з'явиться у виразі на зразок
state = IN;
її замістить 1.
Ми побачимо дві функції, по обробці назв і текстів заміни. install(s,t) реєструє в таблиці назву s і текст заміни t; s із t, це просто символьні ланцюжки. lookup(s) шукає s у таблиці, і повертає покажчик на місце, де його знайдено, або NULL, як такого ланцюжка там немає.
Використовується алгоритм гешованого пошуку — новоприбуле ім'я перетворено на невелике додатнє ціле, яке пізніше використовується як індекс масиву покажчиків. Елемент масиву вказує на початок суцільного списку блоків з описами назв, що відповідають цьому хеш-значенню.NULL означає, що жодне ім'я не відповідає вказаному значенню.
+-----+ +-----+ +-----+
| *--|---->| *--|----->| 0 |
+-----+ | *--|--> | *--|---> назва
| 0 | | *--|--> | *--|---> визначення
+-----+ +-----+ +-----+
| 0 |
+-----+ +-----+
| *--|---->| 0 |
+-----+ | *--|---> назва
| 0 | | *--|---> визначення
+-----+ +-----+
Блок являється списком всередині структури з покажчиками на назву, текст заміни та наступний блок у списку. Наступний нульовий покажчик позначає кінець списку.
struct nlist { /* запис таблиці: */
struct nlist *next; /* наступний запис в ланцюжку */
char *name; /* визначене ім'я */
char *defn; /* текст заміни */
};
Масив покажчиків, це просто
#define HASHSIZE 101
static struct nlist *hashtab[HASHSIZE]; /* таблиця покажчиків */
Функція гешування, використовувана обома, lookup та install, додає кожне значення символу ланцюжка до зашифрованої комбінації попередніх, і повертає остаток поділу на розмір гешу. Це не найкраща з можливих функцій гешування, але вона коротка й ефективна.
/* hash: утворює геш-значення для ланцюжка s */
unsigned hash(char *s)
{
unsigned hashval;
for (hashval = 0; *s != '\0'; s++)
hashval = *s + 31 * hashval;
return hashval % HASHSIZE;
}
Беззнакова арифметика забезпечує додатнє геш-значення.
Процес гешування видасть початковий індекс для масиву hashtab; якщо ланцюжок існує, його можна буде знайти у списку блоків, які там беруть свій початок. Пошук здійснюється функцієюlookup. Якщо lookup знайде відповідний запис, вона поверне покажчик на нього; якщо ні —NULL.
/* lookup: шукає s у hashtab */
struct nlist *lookup(char *s)
{
struct nlist *np;
for (np = hashtab[hash(s)]; np != NULL; np = np->next)
if (strcmp(s, np->name) == 0)
return np; /* знайдено */
return NULL; /* не знайдено */
}
Цикл for функції lookup є стандартною ідіомою проходження вздовж зв'язного списку:
for (ptr = head; ptr != NULL; ptr = ptr->next)
...
функція install використовує lookup, щоб визначити, чи назва, яка додається вже присутня; якщо так, то нове визначення витіснить старе. У протилежному випадку, буде створено новий запис. install поверне NULL, якщо з якоїсь причини не залишилося місця для нового запису.
struct nlist *lookup(char *);
char *strdup(char *);
/* install: додає (name, defn) до hashtab */
struct nlist *install(char *name, char *defn)
{
struct nlist *np;
unsigned hashval;
if ((np = lookup(name)) == NULL) { /* not found */
np = (struct nlist *) malloc(sizeof(*np));
if (np == NULL || (np->name = strdup(name)) == NULL)
return NULL;
hashval = hash(name);
np->next = hashtab[hashval];
hashtab[hashval] = np;
} else /* already there */
free((void *) np->defn); /*free previous defn */
if ((np->defn = strdup(defn)) == NULL)
return NULL;
return np;
}
Вправа 6-5. Напишіть функцію undef, яка би видаляла назву та визначення з таблиці, утворюваної функціями lookup та install.
Вправа 6-6. Втільте просту версію (без аргументів) оброблювача визначень #define (який можна би було використовувати із C-програмами), основуючись на функціях із цього розділу. Вам можливо доведеться згадати функції getch і ungetch.