Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Зубенко, Омельчук - Програмування. Поглиблений курс

.pdf
Скачиваний:
58
Добавлен:
07.03.2016
Размер:
5 Мб
Скачать
☆

Розділ ІІІ. МОВИ ПРОГРАМУВАННЯ С ТА С++

У мові C визначено такі директиви препроцесора:

#define

#endif

#ifdef

#line

#elif

#error

#ifndef

#pragma

#else

#if

#include

#undef

3.9.2. МАКРОПІДСТАНОВКИ

Макрос (скорочення від макропідстановка) – це механізм заміни заданого ідентифікатора (імені макросу), можливо параметризовано- го, на пов'язану з ним послідовність символів. Макроси задаються директивою, що має такий вигляд:

<макрос>::=#define (<простий-макрос>| <параметризований-макрос>) <простий-макрос>::=<ім'я-макросу> <тіло-макросу> <параметризований-макрос>::= <ім'я-макросу>\(<параметр>{,<параметр>}\)<тіло-макросу> <і'мя-макросу>::=<ідентифікатор> <параметр>::=<ідентифікатор> <тіло-макросу>::=<послідовність-символів>

Тіло макросу може бути довільним фрагментом C-програми, у тому числі й порожнім. У випадку простого макросу всі подальші входження його імені в текст програми як лексеми препроцесор замінить на тіло макросу – відповідну послідовність символів. Якщо тіло порожнє, то всі входження імені макросу в текст програму усуваються. Якщо ім'я мак- росу є частиною літерала, то заміна не відбудеться. Наприклад:

#define begin {/*усі входження begin замінити на {*/

#define end} /*усі входження end замінити на }*/ main()

begin int_begin=0;

if (…) begin ………… end else return 0;

end

На виході препроцесора цей фрагмент матиме вигляд:

main() {int_begin=0;

if(…) {…………}

else return 0;

}

411

ПРОГРАМУВАННЯ

Допускаються ланцюжки макропідстановок. Наприклад:

#define Х 10

#define Y X+2 int main()

{

int i;

…

i=Y;

printf("X Y");

...

}

Тоді препроцесор спочатку здійснює таке перетворення:

# define Y 10+2 int main()

{

int i;

…

i=Y;

printf("X Y");

....

}

На виході препроцесора цей фрагмент матиме вигляд:

int main()

{

int i;

…

i=10+2; printf("X Y");

...

}

Нехай є програма, в якій визначаються масив і кілька процедур, що отримують доступ до нього. Замість того, щоб задавати розмір масиву безпосередньо в тексті програми, доцільно за допомогою ди- рективи #define ввести для нього ім'я й конкретне значення. Тоді для зміни розміру масиву необхідно буде лише змінити це значення в директиві й перекомпілювати програму:

#define SIZE 100 /* ... */

float vect[SIZE];

412

Розділ ІІІ. МОВИ ПРОГРАМУВАННЯ С ТА С++

/* ... */

for(i=0; i<SIZE; i++) printf("%f", vect[i]); /* ... */

for(i=0; i<SIZE; i++) x=+vect[i];

Розмір масиву vect визначається іменем макросу SIZE, тому для зміни розміру потрібно лише змінити визначення SIZE. У результаті при перекомпіляції програми всі звернення до імені макросу будуть автоматично змінені.

Щоб скористатися параметризованим макросом, його необхідно викликати. Виклик макросу має вигляд:

<виклик-макросу>::= <ім'я_макросу>(<фактичний_параметр>{,<фактичний-параметр>}) <фактичний_параметр>::=<послідовність-символів>

Кількість фактичних параметрів у виклику макросу має збігатися з кількістю його параметрів. Дія директиви полягає в заміні всіх його викликів на модифіковане тіло. Модифікація тіла – це заміна всіх входжень параметрів у ньому на їхні фактичні значення. Наприклад:

#define MAX(X,Y) (((X)>(Y))?(X):(Y))

main()

{

int i;

…

i=MAX(i, 100)

....

}

На виході препроцесора цей фрагмент матиме вигляд:

main() {int i;

… i=(((i)>(100))?(i):(100)) ....

}

Увага! Під часу виклику макросу можуть виникати проблеми, як- що в його тілі параметри чи деякі підвирази не взято в дужки ►

Розглянемо простий макрос і опис змінної за його участю:

#define N (-2) int p=4-N;

Якщо константу -2 у тілі макросу не взяти в дужки, то для компіля- тора буде отримано опис int p=4--2;, який є помилковим.

413

ПРОГРАМУВАННЯ

Розглянемо інший параметризований макрос:

#define sqr(X) X*X

Тут виклик макросу sqr(a+2) у коді програми буде замінено препро- цесором на вираз a+2*a+2, який не збігається з (a+2)*(a+2)! Здавало- ся б, правильним має бути такий варіант макросу:

#define sqr(X) (X)*(X)

Однак візьмемо вираз 1/sqr(a). Після препроцесування він перетво- риться на 1/(a)*(a) і після скорочення буде збігатися з 1, а не з 1/(a*а), як мало б бути. Правильно було б записати макрос так:

#define sqr(X) ((X)*(X)).

Макроси з формальними параметрами мають суттєву перевагу пе- ред функціями – вони швидше виконуються (немає потреби витрача- ти ресурси на виклик функцій). Проте, якщо макрос із формальними параметрами має великий розмір, то дублювання коду може занадто збільшити розмір машинної програми.

Область дії директиви #define можна обмежити директивою

#undef:

# undef <ім'я-макросу>

Директива відміняє дію раніше визначеного макросу з даним іменем. Наприклад:

#define SIZE 100 float vect[SIZE];

# undef SIZE

/*тут і далі SIZE вже не визначено*/

Є дві операції препроцесора: # та ##. Вони застосовуються разом із директивою #defіne.

Операція # називається перетворенням на рядок (перетворює за- пис аргументу на рядок). Розглянемо програму:

#іnclude <stdіo.h> #defіne mkstr(s)#s

іnt maіn()

{

prіntf(mkstr(TEST));

414

Розділ ІІІ. МОВИ ПРОГРАМУВАННЯ С ТА С++

return 0;

}

Препроцесор перетворює виклик prіntf(mkstr(TEST)); на виклик prіntf("TEST");.

Операція ## називається конкатенацією (склеює дві лексеми в од- ну). Розглянемо програму:

#іnclude <stdіo.h> #defіne cont(a,b) a##b іnt maіn()

{

іnt x1=10;

prіntf("%d", cont(x,1)); return 0;

}

Препроцесор перетворює виклик prіntf("%d", concat(x,1));

на виклик prіntf("%d", x1);.

3.9.3. ВКЛЮЧЕННЯ ФАЙЛІВ

Директива має два варіанти:

1)#include <file> – для файла із системної бібліотеки;

2)#include "file" – для довільного файла (указується його ім'я). <file> задає ім'я файла за правилами ОС і може містити його мар-

шрут у файловій системі. Якщо ім'я файла зазначене в лапках, то йо- го пошук здійснюється відповідно до заданого маршруту, а за його відсутності – у поточному каталозі. Якщо ім'я файла задане в кутових дужках, то пошук відбувається в стандартних директоріях ОС, що задають командою PATH. Наприклад:

#include <stdio.h> #include "c:\MyFile.h"

Заголовні файли можна включати в довільному порядку і скільки завгодно разів.

Файли, імена яких розташовані в директивах #іnclude, можуть, у свою чергу, містити інші директиви #іnclude. Вони називаються вкладеними директивами #іnclude. Кількість припустимих рівнів вкладеності в різних компіляторах може бути різною. У стандарті С89 передбачено, що компілятори мають допускати не менше 8 таких рі- внів, а в стандарті С99 – не менше 15 рівнів вкладеності.

415

ПРОГРАМУВАННЯ

Директива #іnclude широко використовується для включення в програму заголовних файлів, що містять прототипи бібліотечних фун- кцій, тому більшість C-програм розпочинаються з неї.

3.9.4. УМОВНА КОМПІЛЯЦІЯ

#if, #else, #endif, #ifdef, #ifndef

Є кілька директив, що дають можливість вибірково компілювати частини коду програми. Подібну компіляцію називають умовною. Во- на може бути застосована, наприклад, для компілювання програми, яка має різні версії:

#if <ціл-конст-вираз>

…text-1 …. #else

…text-2 … #endif

Якщо значення цілого виразу істинне, то компілюється text-1, інакше – text-2.

Інша форма:

#if <ціл-конст-вираз> … text ….

#endif

Якщо значення виразу істинне, то компілюється text.

Значення константних виразів мають бути обчислені під час ком- піляції, тому в них можуть бути тільки раніше визначені препроцесо- рні та звичайні константи, але не змінні:

/*Простий приклад директиви #іf*/

#іnclude <stdіo.h>

#defіne MAX 100 іnt maіn(){

#іf

prіntf("Компілює фрагмент для MAX>50"); #else

prіntf("Компілює фрагмент для MAX ≤50"); #endіf

return 0;

}

416

Розділ ІІІ. МОВИ ПРОГРАМУВАННЯ С ТА С++

Ця програма виводить на екран повідомлення:

Компілює фрагмент для MAX ≤50

Директива #elіf означає "else іf". Після #elіf міститься <ціл- конст-вираз>; якщо він істинний, то компілюється відповідний блок і більше не перевіряються жодні вирази #elіf, якщо ні – перевіряється наступний #elіf. У загальному вигляді:

#if <ціл-конст-вираз_1> … text-1 …

#elіf <ціл-конст-вираз_2>

…text-2 …

#elіf <ціл-конст-вираз_N>

…text-N …

#endif

Відповідно до стандарту С89 директиви #іf та #elіf можуть місти- ти 8 рівнів вкладеності, а стандарт С99 дозволяє використовувати 63 рівні. При вкладеності кожна директива #endіf, #else або #elіf приєднується до найближчої директиви #іf або #elіf. Наприклад, правильним є такий фрагмент коду:

#іf MAX>100

#іf SERІAL_VERSІON іnt port=198;

#elіf

іnt port=200; #endіf

#else

char out_buffer[100]; #endіf

Інший спосіб умовної компіляції – це використання директив

#іfdef та #іfndef, що відповідно означають "іf defіned" ("якщо ви- значено") та "іf not defіned" ("якщо не визначено"). Загальний ви-

гляд #іfdef:

#іfdef <ім'я_макросу>

… text-1 ….

#endіf

Блок коду буде компілюватися, якщо ім'я макросу було визначене раніше в директиві #defіne. Загальний вигляд директиви #іfndef:

417

ПРОГРАМУВАННЯ

#іfndef <ім'я_макросу> … text-1 ….

#endіf

Блок коду буде компілюватися, якщо ім'я макросу ще не визначене

вдирективі #defіne.

В#іfdef та #іfndef можна використовувати директиви #else або

#elіf:

#іfdef <ім'я_макросу> … text-1 ….

#elіf <ім'я_макросу_2>

…text-2 ….

#elіf <ім'я_макросу_N>

…text-N ….

#endіf

Приклад такого використання наведено в програмі:

#іnclude <stdіo.h> #defіne TED 10

іnt maіn()

{

#іfdef TED

prіntf("Привіт, Тед\n");

#else

prіntf("Привіт, хто-небудь \n");

#endіf

#іfndef RALPH

prіntf("А RALPH не визначений \n");

#endіf return 0;

}

У стандарті C89 допускається не більше 8 рівнів вкладеності

#іfdef та #іfndef, у С99 – не більше 63.

3.9.5. ДИРЕКТИВИ #error, #line ТА #pragma

Директива #error змушує компілятор припинити компіляцію. Її за- гальний вигляд:

#еrrоr <повідомлення-про-помилку>

418

Розділ ІІІ. МОВИ ПРОГРАМУВАННЯ С ТА С++

Наприклад: #еrrоr TEST ERROR

Коли зустрічається директива #error, то виводиться повідомлення про помилку.

Директива #lіne змінює вміст __LІNE__ та __FІLE__, які є зарезер- вованими ідентифікаторами в компіляторі. У першому зберігається номер рядка коду, що компілюється в даний момент. Другий іденти- фікатор – це рядок, що містить ім'я вихідного файла, який компілю- ється. Директива #lіne має вигляд:

#lіne <номер> <"ім'я-файла">,

де номер – додатне ціле число, що стає новим значенням __LІNE__, а необов'язкове ім'я-файла – будь-який припустимий ідентифікатор файла, що стає новим значенням __FІLE__.

Директива #pragma – це обумовлена реалізацією директива, що до- зволяє передавати компілятору різні інструкції. Її можливості зазна- чаються в документації для компілятора.

*Література для CР: препроцесор – [55, 102, 132, 133].

Контрольні запитання та вправи

1.Що таке препроцесор?

2.Що таке директива препроцесора?

3.Що таке простий макрос?

4.Що таке параметризований макрос?

5.Як викликається параметризований макрос?

6.Для чого використовуються макроси? Навести приклади за- стосування макросів.

7.Пояснити різницю між функцією й параметризованим мак- росом.

8.Що таке умовна компіляція?

9.Описати директиви умовної компіляції.

10.Описати операції перетворення в рядок # та конкатенації

##.Навести власні приклади їх застосування.

11.Як діють директиви #line, #error та #pragma?

12.Порівняти значення викликів стандартної функції sqr і ма-

кросу #define sqr(X) ((X)*(X)) на аргументі a++, а та-

кож значення змінної а після цих викликів. Пояснити розбі- жності в значеннях.

13.Написати макрос tree_node (data_type) для опису струк-

тури вузла бінарного дерева, в якому тип вершини задається параметром data_type.

419

ПРОГРАМУВАННЯ

14.Знайти й виправити помилки в директивах 1)-3):

#include \<stdio.h> #include <stdlib>

#define begin {;/*усі входження begin замінити на {*/ #define end} /*усі входження end замінити на}*/

15.Навести приклад застосування умовної компіляції для орга- нізації виведення налагоджувальної інформації на екран.

3.10.Порівняльний аналіз мов C та C++

¾Стандартні бібліотеки С++

¾Область дії змінних

¾Простір імен

¾Специфікатори класів пам'яті

¾Введення-виведення в мові C++

¾Робота з функціями в C++

¾Константи й типи даних мови C++

¾Посилання Керування пам'яттю

Ключові слова: стандарти мови, С++, простір імен, простір імен std, операто- ри namespace та using, специфікатор mutable, бібліотека iostream, операції стан-

дартного введення >> і виведення <<, об'єкти cin, cout та cerr, бібліотеки для роботи з файлами: fstream, ofstream та ifstream, методи seekg() та seekp(), прототип функцій у С++, аргументи функцій за умовчанням, вбудована функція, перевантажена функція, посилання, посилання як параметр і результат роботи функцій, оператори

new(), delete(), тип bool, бібліотека рядків <string>.

Програми, наведені до цього, створювалися на основі підпрограм. Такий процедурно-орієнтований підхід не завжди забезпечує потрібний рівень надійності та гнучкість програмування. Ускладнення розв'язува- них задач стимулювало розвиток іншого підходу – об'єктно- орієнтованого. Цей підхід уже згадувався у вступі й підрозд. 2.2.2, 2.7.3.

ООП на сьогодні є абсолютним лідером у прикладному програмуван- ні. Разом із тим у системному програмуванні досі переважає процедур- на парадигма, і основною мовою є мова C. При взаємодії системного та прикладного рівнів ОС дедалі більше застосовуються мови ООП.

420