C++. Препроцесор. Загальні відомості. Директиви препроцесора. Огляд

Препроцесор. Загальні відомості. Директиви препроцесора. Огляд.
Директиви #define, #error, #include, #undef, #import, #using, #pragma, #line


Зміст


1. Препроцесор. Призначення. Директиви

У мові програмування C++ препроцесор – це частина компілятора, яка керує формуванням вихідного коду в об’єктний. Препроцесор C++ успадкований з мови програмування C. Препроцесор має набір команд, які називаються директивами препроцесора. З допомогою цих директив препроцесор керує змінами в трансляції вихідного коду в об’єктний.

Будь-яка директива препроцесора починається з символу #. У C++ препроцесор містить наступні директиви:

  • #define;
  • #if;
  • #endif;
  • #undef;
  • #error;
  • #else;
  • #ifdef;
  • #line;
  • #include;
  • #elif;
  • #ifndef;
  • #pragma;
  • #using;
  • #line;
  • # або NULL.

 

2. Директива #define. Визначення імені макросу

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

Загальний вигляд використання директиви наступний

#define macro_name character_sequence

тут

  • macro_name – ім’я, яке буде використано в тексті програми;
  • character_sequence – послідовність символів, яка буде замінювати ім’я macro_name кожного разу як воно зустрінеться у програмі.

Приклад.

#include <iostream>
using namespace std;

// Піднести x до степеня 4
#define ABC(x) x*x*x*x

// Обчислити довжину лінії за двома точками
#define LENGTH_LINE(x1, y1, x2, y2) std::sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2))

void main()
{
  // 1. Використати макрос ABC
  int t = 3;
  int x = ABC(t); // x = 81
  cout << "x = " << x << endl;

  // 2. Використати макрос LENGTH_LINE
  double x1, y1, x2, y2;
  x1 = 3.8;
  y1 = 2.7;
  x2 = -1.4;
  y2 = 5.5;
  double len = LENGTH_LINE(x1, y1, x2, y2);
  cout << "len = " << len << endl;
}

 

3. Директива #error. Відображення повідомлення про помилку

Використання директиви #error змушує компілятор зупинити компіляцію. Ця директива використовується для відлагодження програми.

Загальна форма використання директиви #error має вигляд

#error error_message

де

  • error_message – текст повідомлення про помилку.

Приклад.

У прикладі компіляція програми буде виконуватись до директиви #error. Далі компіляція програми зупиниться і видасть повідомлення про помилку

#error: Compilation error

Таким чином *.exe-файл створено не буде.

#include <iostream>
using namespace std;

void main()
{
  // Виконання обчислень
  double a = 3, b = 10, c;

  c = a * b + 5;

  // Вивести повідомлення про помилку
#error Compilation error.
}

 

4. Директива #include. Включення заголовку або іншого вихідного файлу

З допомогою директиви #include можна підключати до вихідного тексту поточного файлу інші зовнішні файли. Текст цих зовнішніх файлів буде використовуватись при формуванні виконавчого модуля вихідного файлу.
У свою чергу, файли, що включаються, в своєму коді також можуть містити директиви #include. Ці директиви називаються вкладеними. Стандарт мови C++ допускає до 256 рівнів вкладення.

У найбільш загальному випадку підключення файлу з іменем filename.h може здійснюватись одним з двох способів:

#include "filename.h"

або

#include <filename.h>

Наявність лапок “” чи кутових обмежувачів <> визначає спосіб пошуку файлу для його підключення.
При першому способі (“filename.h”) пошук файлу здійснюється в каталозі з поточною програмою (робочому каталозі). Якщо файл не знайдено, то використовується другий спосіб пошуку файлу (з допомогою обмежувачів <>).

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

При підключенні файлів, власно розроблені бібліотеки файлів включаються в подвійні лапки “”, а файли стандартної бібліотеки включаються в обмежувачі <>.

При проектуванні програми, яка розміщується в одному файлі, доброю практикою вважається розбивання програмного коду на два файли:

  • файл з розширенням *.h. Тут містяться оголошення (тільки оголошення) функцій, які надаються в загальний доступ (доступні для клієнта);
  • файл з розширенням *.cpp. Тут містяться реалізації всіх розроблених функцій. Сюди можуть входити не тільки функції, які були надані для загального доступу, але й будь-які інші додаткові внутрішні функції проекту.

Приклад.

// Підключення стандартних бібліотек time.h, iostream
#include <time.h>
#include <iostream>

// Підключення простору імен
using namespace std;

// Підключення користувацького файлу my_file.h
#include "my_file.h"

void main()
{

}

 

5. Директива #undef

Директива #undef використовується для видалення імені макросу, яке було визначене директивою #define. Після використання директиви, ім’я макросу, що видаляється, стає невизначеним.

Загальна форма директиви #undef наступна

#undef macros_name

тут

  • macros_name – ім’я макросу, яке потрібно видалити (зробити невизначеним).

Приклад.

#include <iostream>
using namespace std;

// Директива #undef

// Макрос, що множить два числа між собою
#define Mult2(x, y) (x * y)

// Макрос, що додає два числа
#define Add2(x, y) (x + y)

void main()
{
  // Перевірка, чи існує ім'я Mult2
#ifdef Mult2
  double res = Mult2(2.5, 7.8);
  cout << "2.5 * 7.8 = " << res << endl;
#endif

  // Відмінити ім'я Add2 - директива #undef
#undef Add2

  // Перевірка, чи існує ім'я Add2
#ifdef Add2
  int a = 8, b = 9;
  int resAdd = Add2(a, b);
  cout << "a + b = " << resAdd << endl;
#else
  cout << "The Add2 name is undefined." << endl;
#endif
}

Результат

2.5 * 7.8 = 19.5
The Add2 name is undefined.

 

6. Директива #import. Включення відомостей з бібліотеки типів

З допомогою директиви #import можна включати відомості з бібліотеки типів (TLB – type library). У даному контексті бібліотека типів є ієрархічним сховищем інформації про можливості Active-X сервера, яка зберігається як файл з розширенням *.tlb або *.olb.

Загальна форма оголошення директиви #import може бути такою

#import "filename" [attributes]
#import <filename> [attributes]

тут

  • filename – ім’я файлу, що містить бібліотеку типів. Це може бути файл з розширенням *.tlb, *.olb, *.dll або *.exe. Це може бути також будь-який інший формат файлу, що є зрозумілим для функції API LoadTypeLib();
  • attributes – атрибути директиви. Ці атрибути вказують, що компілятор змінює вміст заголовку бібліотеки типів. Більш детальну інформацію про атрибути #import можна знайти в документації на сайті learn.microsoft.com.

Приклад.

#import "MyTypeLib"

 

7. Директива #using. Включення *.dll, *.obj, *.exe, *.net module файлів

Директива #using виконує імпорт метаданих у програму, яка була скомпільована з параметром /clr. Це означає, що використання директиви може бути тільки в режимі C++/CLI.

Загальна форма використання директиви #using наступна

#using filename [as_friend]

тут

  • filename – ім’я файлу який має розширення *.dll, *.exe, *.net module або *.obj.

Наприклад, що підключити динамічну бібліотеку з іменем “MyLibrary.dll” потрібно виконати такий код

#using <MyLibrary.dll>

 

8. Директива #pragma. Задавання компілятору функцій виконання

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

Директива #pragma має багато особливостей використання та декілька реалізацій. Загальна форма однієї з поширених реалізацій наступна

#pragma token_string

тут

  • token_string – так званий рядок токена, який може бути різних значень. Цей рядок є набором символів, що визначають набір інструкцій та їх аргументів. До токенів відносять, наприклад, once, alloc_text, component, auto_inline, endregion, loop тощо.

Детальний розгляд особливостей реалізації директиви в поєднанні з тим чи іншим токеном не є предметом даної теми.

Приклад.

У прикладі задається вказівка компілятору включити поточний файл заголовку тільки один раз у випадку, коли буде відбуватись компіляція файлу з вихідним кодом.
Наприклад, файл заголовку – це файл з іменем “MyClass.h”, файл з вихідним кодом – це файл “MyClass.cpp”.

// Файл "MyClass.h"
#pragma once

 

9. Директива #line. Встановити рядок на назву файлу в повідомленнях про помилку

Використання директиви #line дає вказівку компілятору задати (змінити) номери рядка та назву файлу, що виводяться у повідомленнях про помилку.
При виникненні помилки, компілятор запам’ятовує номер рядка помилки та назву файлу відповідно в макросах __LINE__ та __FILE__. Після виклику директиви #line значення цих макросів змінюється на ті, що були задані у цій директиві.
Директива #line може бути використана в одній з двох можливих загальних форм:

#line digit_sequence

або

#line digit_sequence [filename]

де

  • digit_sequence – цілочисельна константа в діапазоні від 0 до 2147483647 включно. Ця константа задає номер рядка і присвоюється макросу __LINE__ після виклику директиви;
  • filename – необов’язковий параметр, що є іменем файлу, який виводиться у повідомленні про помилку та записується в макросі __FILE__. Якщо filename опущений, то попереднє ім’я файлу залишається без змін.

Директива #line як правило використовується генераторами програм. У цих програмах-генераторах на основі виконання (не виконання) деякого твердження потрібно задавати текст повідомлення про помилку та посилатись на відповідний вихідний файл.

Приклад.

#include <iostream>
using namespace std;

int main()
{
  // Вивести значення макросів __LINE__ та __FILE__
  cout << "The value of __LINE__: " << __LINE__ << endl;
  cout << "The value of __FILE__: " << __FILE__ << endl;

  // Встановити нове значення макросу __LINE__
#line 12
  cout << "The value of __LINE__ after changes: " << __LINE__ << endl;
  cout << "The value of __FILE__ after changes: " << __FILE__ << endl;

  // Встановити нове значення макросів __LINE__ та __FILE__
#line 122 "source2.cpp"
  cout << "The value of __LINE__ after changes 2: " << __LINE__ << endl;
  cout << "The value of __FILE__ after changes 2: " << __FILE__ << endl;
}

Результат

The value of __LINE__: 7
The value of __FILE__: D:\Programs\C++\Project10\Source.cpp
The value of __LINE__ after changes: 12
The value of __FILE__ after changes: D:\Programs\C++\Project10\Source.cpp
The value of __LINE__ after changes 2: 122
The value of __FILE__ after changes 2: D:\Programs\C++\Project10\source2.cpp

 


Споріднені теми