- •Передмова
- •Передмова до першого видання
- •Вступ
- •Вступний урок
- •Твердження for
- •Масиви
- •Аргументи - виклик за значенням
- •Типи, оператори та вирази
- •Константи
- •Оголошення
- •Оператори приросту та спаду
- •Оператори та вирази присвоєння
- •Вирази умов
- •Керування потоком
- •Вирази та блоки
- •If-else
- •Else if
- •Switch
- •Цикли while та for
- •Файли заголовка
- •Препроцесор C
- •Покажчики та масиви
- •Покажчики й адреси
- •Покажчики та масиви
- •Арифметика адрес
- •Аргументи командного рядка
- •Структури
- •Масиви структур
- •Покажчики на структури
- •Typedef
- •Сполуки
- •Ungetc
- •Виконання команд
- •Генератор випадкових чисел
- •Open, creat, close, unlink
98 |
РОЗДIЛ 4. ФУНКЦIЇ ТА СТРУКТУРА ПРОГРАМ |
swap(v, left, last); |
/* вiдновити елемент подiлу */ |
qsort(v, left, last-1); qsort(v, last+1, right);
}
Ми перемiстили операцiю переставляння в окрему функцiю swap, оскiльки вона вiдбувається три рази в qsort.
/* swap: |
помiняти мiсцями |
v[i] i v[j] */ |
void swap(int v[], int i, int |
j) |
|
{
int temp;
temp = v[i]; v[i] = v[j]; v[j] = temp;
}
Стандартна бiблiотека включає власну версiю qsort, спроможну сортувати об’є- кти будь-якого типу. Рекурсiя може займати мiсце, оскiльки десь потрiбно втримувати стек значень для обробки. Також вона не є швидшою. Але рекурсивний код компактнiший i часто набагато простiший у написаннi i розумiннi, чим нерекурсивний еквiвалент. Рекурсiя особливо зручна для рекурсивно-визначених даних на зразок деревовидних структур; ми побачимо гарний приклад цього у Роздiлi 6.6.
Вправа 4-12. Пристосуйте iдеї printd для написання рекурсивної версiї itoa; тобто перетворiть цiле у ланцюжок, шляхом виклику рекурсивної функцiї.
Вправа 4-13. Напишiть рекурсивну версiю функцiї reverse(s), яка обертає ланцюжок s на мiсцi.
4.11 Препроцесор C
C забезпечує певними додатковими мовними засобами через посередництво препроцесора, який концептуально є окремим першим кроком в компiляцiї. Двi, риси, якими найчастiше користуються, це #include — для включення вмiсту iншого файла пiд час компiляцiї i #define — для замiни лексеми певною послiдовнiстю знакiв. Iншими властивостями, розглянутими у цьому роздiлi, є обумовлена компiляцiя i макроси з аргументами.
4.11.1Включення файлiв
Включення файлiв полегшує використання наборiв #define i оголошень (серед iнших речей). Будь-який рядок вихiдного тексту, котрий має форму
4.11. ПРЕПРОЦЕСОР C |
99 |
#include "filename"
або
#include <filename>
замiнюється на вмiст файла filename. Якщо назву файла взято у лапки, його пошук, типово, почнеться з того мiсця, де перебуває вихiдний текст програми; якщо його там не знайдено, або якщо назву файла включено у < й >, пошук слiдує правилам, визначеним системою, щоб знайти його. Включений файл може й самий мiстити рядки
#include.
Часто, ви знайдете декiлька рядкiв #include на початку вихiдного файла, для включення загальних тверджень #define i оголошень extern, або для доступу до оголошень прототипiв функцiй для бiблiотечних функцiй з таких заголовкiв як <stdio.h>, наприклад. (Якщо бути точним, заголовки не обов’язково повиннi бути файлами; деталi щодо звернення до заголовкiв визначаються системою).
#include — це переважний спосiб зв’язування разом оголошень у великих програмах. Вiн гарантує, що всi вихiднi файли забезпечено тими самими визначеннями i оголошенням змiнних, усуваючи можливiсть особливо неприємних вад. Звичайно, якщо включений файл змiнено, то всi файли, якi залежать вiд нього, слiд перекомпiлювати.
4.11.2Замiна макросiв
Визначення макросу має форму
#define назва текст-замiни
Це вимагає найпростiшої замiни макросу — наступнi появи лексеми назва буде замiнено текстом замiни . Назва в #define має ту саму форму, що й назви змiнних; текст замiни може бути довiльним. Зазвичай текст замiни розмiщено на тому самому рядковi, але якщо треба продовжити його на наступних, додайте зворотню похилу укiнцi кожного рядка, який ви хочете продовжити. Зона дiї назви, означеної #define, починається з пункту, де її було означено i аж до кiнця файла, який буде скомпiльовано. Визначення можуть вживати попереднi визначення. Замiни матимуть мiсце тiльки для лексем i не вiдбуваються всерединi ланцюжкiв взятих у лапки. Скажiмо, якщо YES — це означена назва, замiна стане неможливою у випадку printf(¨YES¨) або YESMAN. Назву можна пов’язати з будь-яким текстом замiни. Наприклад
#define |
forever |
for(;;) /* нескiнчений цикл */ |
100 РОЗДIЛ 4. ФУНКЦIЇ ТА СТРУКТУРА ПРОГРАМ
визначає для нескiнченого циклу нове слово — forever.
Можна також означити макроси з аргументами, тож текст замiни зможе вiдрiзнятися для рiзних викликiв макросу. Для прикладу, створимо макрос пiд назвою
max: |
|
#define |
max(A, B) ((A) > (B) ? (A) : (B)) |
Хоч це й виглядає нiби виклик функцiї, виклик max розкривається у вбудований код. Кожна поява формального параметра (у цьому випадку A й B) замiнюватиметься на вiдповiднi дiйснi аргументи. Таким чином, рядок
x = max(p+q, r+s);
замiнюється на
x = ((p+q) > (r+s) ? (p+q) : (r+s));
Доки аргументи вживаються несуперечливо, цей макрос служитиме для будьякого типу даних; немає потреби у вiдмiнному max для iншого типу даних, як це вiдбувається у функцiях.
Якщо ви розглянете розкриття max, то можете помiтити деякi пастки. Вирази обчислено двiчi; це може бути погано, якщо вони включатимуть побiчнi ефекти, такi як оператори приросту або ввiд i вивiд. Так, наприклад,
max(i++, j++); |
/* НЕПРАВИЛЬНО */ |
здiйснює прирiст бiльшого значення двiчi. Треба також звертати увагу на дужки, щоб упевнитись, що порядок обчислень збережено; уявiть, що станеться якщо макрос
#define square(x) |
x * x |
/* НЕПРАВИЛЬНО */ |
викликати як square(z+1).
Не зважаючи на це, макроси кориснi. Один з прикладiв їхнього використання на практицi можна знайти в <stdio.h>, де getchar i putchar часто означено як макроси, щоб запобiгти втратi робочого часу на виклик функцiї для кожного опрацьованого знака. Функцiї у <ctype.h> також часто втiлено як макроси.
Назви можна скасувати за допомогою #undef, зазвичай, щоб упевнитись, що якась функцiя є справдi функцiєю а не макросом:
#undef getchar
int getchar(void) { ... }
4.11. ПРЕПРОЦЕСОР C |
101 |
Формальнi параметри не замiнюються, якщо їх було взято у лапки при означеннi макросу. Проте, якщо попереду назви параметра стоїть знак # у текстi замiни, комбiнацiя розшириться до взятого в лапки ланцюжка, у якому параметр замiниться на фактичний аргумент. Це можна комбiнувати зi зчепленням ланцюжкiв, наприклад для вiдлагодження макросу виводу:
#define |
dprint(expr) |
printf(#expr " = %g\n", expr) |
Якщо його викликати як
dprint(x/y)
макрос розкриється у
printf("x/y" " = %g\n", x/y);
ланцюжки буде зчеплено, тож отримаємо
printf("x/y = %g\n", x/y);
Всерединi отриманого аргументу, кожний знак ¨ замiнюється на ¨ i кожний на, тож результат буде допустимою ланцюжковою константою.
Оператор препроцесора ## дає можливiсть зчепити дiйснi аргументи пiд час розкриття макросу. Якщо параметр у текстi замiни є сумiжним з ##, цей параметр буде замiнено на дiйсний аргумент, ## i оточуючi пробiли усунуто i результат переглянуто. Наприклад, наступний макрос paste зчеплює власнi два аргументи:
#define paste(front, back) front ## back
тож paste(name, 1) створить лексему name1.
Правила гнiздованого використання ## досить заплутанi; подальшi деталi ви знайдете у Додатку A.
Вправа 4-14. Означте макрос swap(t,x,y), який мiняє мiсцями два аргументи типу t. (Тут поможе блокова структура коду.)
4.11.3Обумовлене включення файлiв
Iснує також можливiсть керувати самою первинною обробкою — за допомогою умовних виразiв, якi обробить препроцесор. Це дає можливiсть вибiркового включення коду, залежно вiд значень умов, розглянутих пiд час компiляцiї.
102 |
РОЗДIЛ 4. ФУНКЦIЇ ТА СТРУКТУРА ПРОГРАМ |
Рядок #if обчислює константний цiлочисельний вираз (який не може включати sizeof, зведення типiв або констант енумерацiї). Якщо вираз не є нульовим, всi наступнi рядки до #endif або #elif або ж #else буде включено. (Препроцесорний вираз #elif подiбний до esle-if.) Вираз defined(назва ) пiсля #if дорiвнюватиме 1, якщо назву було означено i 0, якщо нi.
Наприклад, щоб упевнитись, що вмiст файла hdr.h включено тiльки один раз, сам вмiст оточується умовою на зразок наступної:
#if !defined(HDR) #define HDR
/* тут йде вмiст hdr.h */
#endif
Перше включення hdr.h визначає iм’я HDR; наступнi включення знаходитимуть це iм’я означеним i перестрибуватимуть далi аж до #endif. Цей стиль можна використати i щоб запобiгти багаторазового включення тих самих файлiв. Якщо послiдовно впроваджувати даний стиль, тодi кожний файл заголовка може самостiйно включати будь-якi iншi файли заголовка, вiд яких вiн залежить, звiльняючи користувача заголовка вiд клопоту про взаємозалежностi.
Наступнi декiлька рядкiв перевiряють змiнну SYSTEM для того, щоб вирiшити, яку саме версiю файла заголовка включити:
#if SYSTEM == SYSV #define HDR "sysv.h"
#elif SYSTEM == BSD #define HDR "bsd.h"
#elif SYSTEM == MSDOS #define HDR "msdos.h"
#else
#define HDR "default.h" #endif
#include HDR
Вирази #ifdef i #ifndef є спецiалiзованою формою, яка перевiряє чи iм’я означено, чи нi. Перший приклад з #if вище можна було також написати як
#ifndef HDR #define HDR
/* тут йде вмiст hdr.h */
4.11. ПРЕПРОЦЕСОР C |
103 |
#endif
104 |
РОЗДIЛ 4. ФУНКЦIЇ ТА СТРУКТУРА ПРОГРАМ |
