Препроцессор — уникальная особенность C++. В C++ существуют такие шаги, как компиляция, компоновка и выполнение для типичной программы. На самом деле, есть еще много других функций в программе на C++, которые необходимо обработать перед передачей программы на компиляцию.
Для этого проводится специальный этап, называемый предварительной обработкой. Предварительная обработка выполняется до процесса компиляции, и специальные функции предварительно обрабатываются. В результате получается расширенная программа на С++, которая затем передается компилятору.
Специальные функции для предварительной обработки идентифицируются с помощью объекта, называемого «Директива препроцессора». Эти директивы препроцессора сообщают компилятору, что определенная информация в программе C++, помеченная директивами препроцессора, должна быть предварительно обработана перед компиляцией.
Обратите внимание, что в C++ все директивы препроцессора начинаются с символа «#». В тот момент, когда препроцессор (часть компилятора) встречает символ #, информация, следующая за символом #, предварительно обрабатывается перед передачей программы компилятору.
В отличие от других операторов C++, директивы препроцессора не заканчиваются точкой с запятой.
Директивы включения файлов
#include
Директива включения файлов #include позволяет нам включать другие файлы в нашу исходную программу. Мы можем включить в нашу программу любой заголовочный файл, содержащий определения различных предопределенных функций, используя эти функции. Мы можем включить файлы заголовков в нашу программу, используя следующий синтаксис:
1 |
#include <filename> |
Пример: #include <iostream>
Заголовок iostream содержит функции, необходимые для потоковой передачи данных ввода/вывода, такие как cout, cin и т. д.
По мере того, как наши программы становятся больше или функциональность усложняется, мы можем захотеть разделить нашу программу на несколько файлов или импортировать функциональность из других файлов. В этом случае мы используем пользовательские файлы. Чтобы включить пользовательские файлы в нашу программу, мы можем использовать следующий синтаксис директивы #include:
1 |
#include “filename” |
Пример: #include «vector_int.h»
Это определяемый пользователем заголовочный файл, который мы намерены включить в нашу программу, чтобы использовать его функциональные возможности.
В приведенном ниже примере кода показано использование директивы #include:
1 2 3 4 5 6 |
#include <iostream> using namespace std; int main() { cout<<"This is an example demonstrating inclusion directive #include"; } |
Это пример, демонстрирующий директиву включения #include.
Как показано выше, мы использовали директиву #include, чтобы включить функциональность заголовка <iostream> в нашу программу.
Директивы определения макросов
#define
Директива #define используется для определения символических констант или макросов в программе на C++.
Общая форма директивы #define следующая:
1 |
#define macro_name replacement code |
Когда препроцессор встречает макрос в программе, препроцессор заменяет этот макрос кодом, определенным с помощью директивы #define, прежде чем код будет передан компилятору.
В приведенном ниже примере кода показана символическая константа RADIUS, определенная с помощью директивы #define:
1 2 3 4 5 6 7 8 9 |
#include <iostream> #define RADIUS 5 using namespace std; int main() { cout<<"Area of a circle : "<<3.142 * RADIUS * RADIUS; } |
Вывод данных:
1 |
Area of a circle : 78.55 |
Как показано в программе, мы можем использовать символическую константу RADIUS в нашем коде, и она будет заменена значением, определенным для нее с помощью директивы #define.
Мы можем использовать директиву #define для определения правильного кода функции. Эти функции обычно являются небольшими функциями.
Пример показан ниже:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <iostream> #define REC_AREA(length, breadth) (length * breadth) using namespace std; int main() { int length = 20, breadth = 5, area; area = REC_AREA(length, breadth); cout << "Area of a rectangle is: " << area; return 0; } |
Вывод данных:
1 |
Area of a rectangle is: 100 |
Здесь с помощью директивы #define мы определили функцию REC_AREA, которая принимает два аргумента, т.е. длину и ширину, и вычисляет площадь прямоугольника. В основной функции мы просто используем этот макрос и передаем ему два аргумента, для получения площади прямоугольника.
#undef
Макросы в программе, определенные с помощью директивы #define, остаются в силе до тех пор, пока они не будут определены с помощью директивы #undef. Как только программа обнаружит #undef, последующее использование макроса (не определенного #undef) приведет к ошибке компиляции.
В приведенной выше программе, если мы просто введем оператор #undef REC_AREA после объявлений целых чисел, программа выдаст ошибку компиляции.
Директивы условной компиляции
Помимо описанных выше директив, C++ также предоставляет следующие директивы, которые можно использовать для условной компиляции кода. Эти директивы можно использовать в аналогичных строках оператора if-else C++.
Например, мы можем установить DEBUG для программы в положение ON или OFF, используя эти условные директивы.
Некоторые из директив условной компиляции, представленных в C++, включают в себя:
- #if
- #elif
- #endif
- #ifdef
- #ifndef
- #else
В приведенной ниже программе показано использование директив условной компиляции в программе на C++:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <iostream> using namespace std; #define DEBUG #define MAX(a,b) (((a)>(b)) ? a : b) int main () { int i, j; i = 100; j = 50; #ifdef DEBUG cout <<"Trace: Start of main function" << endl; #endif cout <<"The maximum is " << MAX(i, j) << endl; #undef MAX //cout <<"The maximum is " << MAX(10,20) << endl; #ifdef DEBUG cout <<"Trace: End of main function" << endl; #endif return 0; } |
Вывод данных:
1 2 3 |
Trace: Start of main function The maximum is 100 Trace: End of main function |
В приведенной выше программе мы используем директиву #ifdef — #endif для определения DEBUG для программы. Затем мы отменили определение функции макроса MAX с помощью директивы #undef. Директива условной компиляции #ifdef — #endif проверяет, установлен ли DEBUG, и если он установлен, то печатает несколько сообщений в программе.
Операторы # и ##
Операторы # и ## — это два специальных оператора, которые соответственно используются для преобразования текстового токена в отображаемую строку и объединения двух токенов.
Ниже приведен пример, демонстрирующий работу этих операторов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <iostream> using namespace std; #define MKSTR( x ) #x #define concat(a, b) a ## b int main () { cout <<"MKSTR(Hello World) = "<< MKSTR(Hello World) << endl; int xy = 100; cout <<"concat(x,y) = "<<concat(x,y); return 0; } |
Вывод данных:
1 2 |
MKSTR(Hello World) = Hello World concat(x,y) = 100 |
В приведенной выше программе мы определяем MKSTR с аргументом x. У него тело №x. Когда мы печатаем этот MKSTR с использованием аргумента «Hello World», мы видим, что из-за #x аргумент преобразуется в строку и отображается на выходе.
Затем мы определили функцию concat с двумя аргументами a и b. В теле указываем a##b. Выражение a##b равно ab. Таким образом, в основной функции, когда мы вызываем concat(x,y), она на самом деле вычисляет значение xy, равное целочисленной переменной, которую мы определили.
Другие директивы
#error
Общий синтаксис директивы #error:
1 |
#error error_message |
#line
#line говорит компилятору изменить номер строки и имя файла, хранящиеся внутри компилятора, на заданный номер строки и имя файла.
1 |
#line digit-sequence [“filename”] |
digit_sequence может быть целочисленной константой.
Пример: #line 200 test.c
В приведенном выше примере для внутреннего сохраненного номера строки установлено значение 200, а имя файла изменено на test.c.
#pragma
#pragma предоставляет компилятору инструкции, определяемые реализацией. Эти инструкции специфичны для компилятора и платформы. Если инструкция не совпадает, директива игнорируется без генерации синтаксической ошибки.
Предопределенные макросы
C++ также определяет множество предопределенных макросов, которые могут использоваться программистами.
Некоторые из этих макросов приведены в таблице ниже:
Предопределенный макрос | Описание |
__FILE__ | Текущее имя файла компилируемой программы |
__DATE__ | Дата перевода исходного кода в объектный код в формате месяц/день/год |
__TIME__ | Время в формате час:минута:секунда, когда программа компилируется |
__LINE__ | Текущий номер строки компилирующейся программы |
__cplusplus | Целочисленная константа, определенная для каждой версии компилятора. |
Следующая программа демонстрирует эти макросы:
1 2 3 4 5 6 7 8 9 10 11 |
#include <iostream> using namespace std; int main () { cout<<"__LINE__ :" << __LINE__ << endl; cout<<"__FILE__ :" << __FILE__ << endl; cout<<"__DATE__ :" << __DATE__ << endl; cout<<"__TIME__ :" << __TIME__ << endl; cout<<"__cplusplus:"<<__cplusplus<<endl; } |
Вывод данных:
1 2 3 4 5 |
__LINE__ :5 __FILE__ :prog.cpp __DATE__ :Apr 15 2019 __TIME__ :12:09:15 __cplusplus:201402 |
Вывод программы выше соответствует объяснению предопределенных макросов выше и не требует пояснений.
Итог
Директивы препроцессора помогают нам писать более эффективные программы и в некоторой степени более читаемые программы. Директивы условной компиляции также позволяют нам различными способами разветвлять вывод нашей программы.
В следующей статье мы поговорим о многопоточности, а также приведем примеры для лучшего понимания.
С Уважением, МониторБанк