Урок №25. Директиви препроцесора

  Юрій  | 

  Оновл. 10 Чер 2021  | 

 392

Препроцесор краще розглядати як окрему програму, яка виконується перед компіляцією. При запуску програми препроцесор переглядає код зверху вниз, файл за файлом, в пошуку директив. Директиви — це спеціальні команди, які починаються з символу # і НЕ закінчуються крапкою з комою. Є кілька типів директив, які ми розглянемо нижче.

Директива #include

Ви вже бачили директиву #include в дії. Коли ви підключаєте файл за допомогою директиви #include, препроцесор копіює вміст файлу, який підключається, в поточний файл відразу після рядка з #include. Це дуже корисно при використанні певних даних (наприклад, попередніх оголошень функцій) відразу в декількох місцях.

Директива #include має дві форми:

   #include <filename>, яка повідомляє препроцесору шукати файл в системних директоріях. Частіше всього ви будете використовувати цю форму при підключенні заголовкових файлів зі Стандартної бібліотеки С++.

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

Директива #define

Директиву #define можна використовувати для створення макросів. Макрос — це правило, яке визначає конвертацію ідентифікатора у вказані дані.

Є два основних типи макросів: макроси-функції і макроси-об’єкти.

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

Макроси-об’єкти можна визначити одним з двох наступних способів:

#define ідентифікатор

чи

#define ідентифікатор текст_заміна

Перше визначення не має ніякого текст_заміна, в той час як друге — має. Оскільки це директиви препроцесора (а не прості стейтменти), то жодна з цих форм не закінчується крапкою з комою.

Макроси-об’єкти з текст_заміна

Коли препроцесор зустрічає макроси-об’єкти з текст_заміна, то будь-яка подальша поява ідентифікатор замінюється на текст_заміна. Ідентифікатор зазвичай пишеться великими літерами з символами підкреслення замість пробілів.

Розглянемо наступний фрагмент коду:

Препроцесор перетворить вищенаведений код в:

Результат виконання:

My favorite number is: 9

Ми розглянемо це детально і те, чому так не варто робити, на наступних уроках.

Макроси-об’єкти без текст_заміна

Макроси-об’єкти також можуть бути визначені і без текст_заміна. Наприклад:

Будь-яка подальше поява ідентифікатора USE_YEN видаляється і замінюється «нічим» (порожнім місцем)!

Це може здатися досить даремним і не вартим використання, однак, це не основне призначення подібних директив. На відміну від макросів-об’єктів з текст_заміна, ця форма макросів вважається прийнятною для використання.

Умовна компіляція

Директиви препроцесора умовної компіляції дозволяють визначити, за яких умов код компілюватиметься, а при яких — ні. На цьому уроці ми розглянемо тільки три директиви умовної компіляції:

   #ifdef

   #ifndef

   #endif

Директива #ifdef (скор. від if defined” = “якщо визначено”) дозволяє препроцесору перевірити, чи було значення раніше визначено за допомогою директиви #define. Якщо так, то код між #ifdef і #endif скомпілюється. Якщо ні, то код буде проігнорований. Наприклад:

Оскільки PRINT_JOE вже був визначений, то рядок std::cout << "Joe" << std::endl; скомпілюється і виконається. А оскільки PRINT_BOB не був визначений, то рядок std::cout << "Bob" << std::endl; не скомпілюється і, відповідно, не виконається.

Директива #ifndef (скор. від if not defined” = “якщо не визначено”) — це повна протилежність #ifdef, яка дозволяє перевірити, чи не було значення раніше визначено. Наприклад:

Результатом виконання цього фрагменту коду буде Bob, тому що PRINT_BOB раніше ніколи не був визначений. Умовна компіляція дуже часто використовується в якості header guards (про них ми поговоримо на наступному уроці).

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

Директиви виконуються перед компіляцією програми: зверху вниз, файл за файлом. Розглянемо наступну програму:

Незважаючи на те, що директива #define MY_NAME "Alex" визначена всередині функції boo(), препроцесор цього не помітить, тому що він не розуміє такі поняття мови C++, як функції. Отже, виконання цієї програми буде ідентично тій, в якій би #define MY_NAME "Alex" було визначено ДО, або відразу ПІСЛЯ функції boo(). Для кращої читабельності коду визначайте ідентифікатори (за допомогою директиви #define) поза функцій.

Після того, як препроцесор завершить своє виконання, всі ідентифікатори (визначені з допомогою директиви #define) з цього файлу відкидаються. Це означає, що директиви дійсні тільки від точки визначення до кінця файлу, в якому вони визначені. Директиви, визначені в одному файлі коду, не впливають на директиви, визначені всередині інших файлів цього ж проекту.

Розглянемо наступний приклад:

function.cpp:

main.cpp:

Результат виконання програми:

Not printing!

Незважаючи на те, що ми оголосили PRINT в main.cpp (#define PRINT), це все одно не має ніякого впливу на що-небудь в function.cpp. Тому, при виконанні функції doSomething(), у нас виводиться Not printing!, так як в файлі function.cpp ми не оголошували ідентифікатор PRINT (за допомогою директиви #define). Це пов’язано з header guards.

Оцінити статтю:

1 Зірка2 Зірки3 Зірки4 Зірки5 Зірок (5 оцінок, середня: 5,00 з 5)
Loading...

Залишити відповідь

Ваш E-mail не буде опублікований. Обов'язкові поля відмічені *