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

Включення файлів і попередження помилок включення

Ви обов'язково створюватимете проекти, що складаються з декількох різних файлів. Традиційно в проектах додатка кожен клас має власний файл заголовка з оголошенням класу (зазвичай такі файли мають розширення .hpp) і файл джерела з кодом виконання методів класу (зазвичай з розширенням .cpp).

Функцію main() програми поміщають у свій власний файл .cpp, а усі файли .cpp компілюються у файли .obj, які потім компонувальник зв'язує в єдину програму.

Оскільки програми зазвичай використовують методи з багатьох класів, основний файл програми міститиме включення багатьох файлів заголовків. Крім того, файли заголовків часто включають інші файли заголовків. Наприклад, файл заголовка з оголошенням похідного класу повинен включити файл заголовка базового класу.

Уявіть собі, що клас Animal оголошується у файлі ANIMAL.hpp. Щоб оголосити клас Dog (який виробляється від класу Animal), слідує у файл DOG.HPP включити файл ANIMAL.hpp, інакше клас Dog не можна буде виробити від класу Animal. Файл заголовка Cat також включає файл ANIMAL.hpp з тієї ж причини.

Якщо існує метод, який використовує обидва класи, - Cat і Dog, то ви зіткнетеся з небезпекою подвійного включення файлу ANIMAL.hpp. Це згенерує помилку в процесі компіляції, оскільки компілятор не дозволить двічі оголосити клас Animal, навіть незважаючи на ідентичність оголошень. Цю проблему можна розв'язати за допомогою директив препроцесора. Код файлу заголовка ANIMAL необхідно укласти між наступними директивами:

##ifndef ANIMAL_HPP

##define ANIMAL_HPP

... // .. // далі слідує код файлу заголовка

##endif

Цей запис означає: якщо лексема ANIMAL_HPP ще не визначена в програмі, продовжуйте виконання коду, наступний рядок якого визначає цю лексему. Між директивою #define і директивою завершення блоку умовної компіляції #endif включається вміст файлу заголовка.

Коли ваша програма включає цей файл вперше, препроцесор читає перший рядок і результат перевірки, звичайно ж, виявляється істинним, тобто до цього моменту лексема ще не була визначена як ANIMAL_HPP. Наступна директива препроцесора #define визначає цю лексему, після чого включається код файлу.

Якщо програма включає файл ANIMAL, HPP удруге, препроцесор читає перший рядок, який повертає значення FALSE, оскільки рядок ANIMAL.hpp вже була визначена. Тому управління програмою переходить до наступної директиви - #else (в даному випадку така відсутня) або #endif (яка знаходиться у кінці файлу). Отже, цього разу пропускається увесь вміст файлу і клас двічі не оголошується.

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

Примечание:Никогда не пошкодить використовувати засоби захисту від багатократного включення. Нерідко вони здатні заощадити години роботи, витрачені на пошук помилок і відладку програми.

У лістингу 3.2 показана програма, повністю готова до компіляції. У ній обчислюється площа прямокутника, після чого результат виводиться на екран.

Лістинг 3.2. Демонстрація використання змінних

1: // Демонстрація використання змінних

2: #include <iostream.h>

3:

4: int main()

5: {

6: unsigned short int Width = 5, Length;

7: Length = 10;

8:

9: // створюємо змінну Area типу unsigned short і ініціалізували її

10: // результатом множення значень змінних Width на Length

11: unsigned short int Area = (Width * Length);

12:

13: cout << "Width:" << Width << "\n";

14: cout << "Length: " << Length << endl;

15: cout << "Area: " << Area << endl;

16: return 0;

17: }

 

Результат:

Width: 5

Length: 10

Area: 50

 

У рядку 2 міститься директива препроцесора include, включающаябиблиотеку iostream, яка забезпечує працездатність об'єкту виведення cout. Власне, програма починає свою роботу в рядку 4.

У рядку 6 змінна Width визначається для зберігання значення типу unsigned short int, і тут же виконується ініціалізація цієї змінної числом 5. У цьому ж рядку визначається ще одна змінна Length такого ж типу, але без ініціалізації. У рядку 7 змінній Length привласнюється значення 10.

У рядку 11 визначається змінна Area типу unsigned short int, яка тут же ініціалізувалася значенням, отриманим в результаті множення значень змінних Width і Length. У рядках 13-15 значень усіх змінних програми виводяться на екран. Зверніть увагу на те, що для розривів рядків використовується спеціальний оператор endl.