Препроцессор C++: как директивы формируют ваш код
Препроцессоры — это директивы, дающие инструкции компилятору предварительно обработать информацию перед началом фактической компиляции.
Все директивы препроцессора начинаются с #, и перед директивой в строке могут стоять только пробельные символы. Директивы препроцессора не являются операторами C++, поэтому не заканчиваются точкой с запятой.
Вы уже видели директиву #include во всех примерах. Этот макрос включает заголовочный файл в исходный файл.
Существует множество директив: #include, #define, #if, #else, #line и др. Рассмотрим важные.
Препроцессор #define
Директива #define создаёт символические константы. Символическая константа называется макросом. Общая форма:
#define имя_макроса текст_замены
Когда эта строка встречается в файле, все последующие вхождения макроса в этом файле заменяются на текст_замены до компиляции. Пример:
#include <iostream>
using namespace std;
#define PI 3.14159
int main() {
cout << "Value of PI :" << PI << endl;
return 0;
}
Если сделать препроцессинг этого кода (компиляция с флагом -E и перенаправление в test.p), внизу файла можно увидеть замену:
$ gcc -E test.cpp > test.p
...
int main() {
cout << "Value of PI :" << 3.14159 << endl;
return 0;
}
Функциональные макросы
С помощью #define можно определить макрос, принимающий аргументы:
#include <iostream>
using namespace std;
#define MIN(a,b) (((a)<(b)) ? a : b)
int main() {
int i = 100, j = 30;
cout << "The minimum is " << MIN(i, j) << endl;
return 0;
}
Результат:
The minimum is 30
Условная компиляция
Несколько директив позволяют компилировать выборочные части исходного кода. Этот процесс называется условной компиляцией.
Условная конструкция препроцессора похожа на структуру выбора if. Пример:
#ifndef NULL #define NULL 0 #endif
Можно компилировать программу для отладки. Включение/выключение отладки через один макрос:
#ifdef DEBUG
cerr << "Variable x = " << x << endl;
#endif
Это заставит оператор cerr компилироваться в программу, если символическая константа DEBUG была определена до директивы #ifdef DEBUG. Можно использовать #if 0 для закомментирования части программы:
#if 0 код, исключённый из компиляции #endif
Пример:
Live Demo#include <iostream>
using namespace std;
#define DEBUG
#define MIN(a,b) (((a)<(b)) ? a : b)
int main() {
int i = 100, j = 30;
#ifdef DEBUG
cerr << "Trace: Inside main function" << endl;
#endif
#if 0
/* Это закомментированная часть */
cout << MKSTR(HELLO C++) << endl;
#endif
cout << "The minimum is " << MIN(i, j) << endl;
#ifdef DEBUG
cerr << "Trace: Coming out of main function" << endl;
#endif
return 0;
}
Результат:
The minimum is 30 Trace: Inside main function Trace: Coming out of main function
Операторы # и ##
Операторы # и ## препроцессора доступны в C++ и ANSI/ISO C. Оператор # преобразует токен текста замены в строку, обрамлённую кавычками.
Рассмотрим макрос:
Live Demo#include <iostream>
using namespace std;
#define MKSTR(x) #x
int main() {
cout << MKSTR(HELLO C++) << endl;
return 0;
}
Результат:
HELLO C++
Как это сработало: препроцессор превращает строку
cout << MKSTR(HELLO C++) << endl;
в
cout << "HELLO C++" << endl;
Оператор ## конкатенирует два токена. Пример:
#define CONCAT(x, y) x ## y
Когда CONCAT встречается в программе, его аргументы конкатенируются и заменяют макрос. Например, CONCAT(HELLO, C++) заменяется на HELLOC++ (без пробела).
#include <iostream>
using namespace std;
#define concat(a, b) a ## b
int main() {
int xy = 100;
cout << concat(x, y);
return 0;
}
Результат:
100
Препроцессор трансформирует
cout << concat(x, y);
в
cout << xy;
Предопределённые макросы C++
C++ предоставляет ряд предопределённых макросов:
| № | Макрос и описание |
|---|---|
| 1 | __LINE__ Содержит текущий номер строки программы при компиляции. |
| 2 | __FILE__ Содержит текущее имя файла программы при компиляции. |
| 3 | __DATE__ Содержит строку вида месяц/день/год — дата трансляции исходного файла в объектный код. |
| 4 | __TIME__ Содержит строку вида час:минута:секунда — время компиляции программы. |
Пример для всех макросов:
Live Demo#include <iostream>
using namespace std;
int main() {
cout << "Value of __LINE__ : " << __LINE__ << endl;
cout << "Value of __FILE__ : " << __FILE__ << endl;
cout << "Value of __DATE__ : " << __DATE__ << endl;
cout << "Value of __TIME__ : " << __TIME__ << endl;
return 0;
}
Результат (пример):
Value of __LINE__ : 6 Value of __FILE__ : test.cpp Value of __DATE__ : Feb 28 2011 Value of __TIME__ : 18:52:48
Язык C
- Освоение директив препроцессора C#: практическое руководство
- Понимание типов данных C++: основы, модификаторы и лучшие практики
- Освоение операторов C++: полное руководство с практическими примерами
- Комментарии в C++: лучшие практики для читаемого и поддерживаемого кода
- Понимание конструкторов в C++: конструкторы по умолчанию, параметризованные и копирования
- Шаблоны классов в C++: практическое руководство
- Предпроцессор C (CPP): как он упрощает компиляцию
- Язык программирования C++: обзор и ключевые особенности
- Константы и литералы в C++: типы, синтаксис и лучшие практики
- Полное руководство по операторам C++: типы, использование и лучшие практики