Директивы препроцессора - это особые инструкции для компилятора, данные перед вызовом компиляции реального кода. Таких директив несколько. Несколько важных категорий таких директив включают (i) определения макросов (ii) включения условий (iii) управление строкой и (iv) директивы об ошибках.
Макроопределения
Определения макросов можно получить с помощью #define
и можно не определять с помощью #undef
Когда препроцессор C ++ встречает директиву #define
, он заменяет любое вхождение идентификатора в остальной части тела на замену. Эта замена может быть выражением, оператором, блоком или просто чем угодно. Препроцессор не понимает C ++, он просто заменяет любое вхождение идентификатора на замену.
#define ARRAY_SIZE 100
int ar1[ARRAY_SIZE];
int ar2[ARRAY_SIZE];
После замены препроцессором ARRAY_SIZE
код становится эквивалентным
int ar1[100];
int ar1[100];
Такое использование #define
в качестве средства определения константы является обычным, но #define
также может работать с параметрами для определения макросов функций:
#define getmax(a,b) a>b?a:b
Вот пример его использования:
#include <iostream>
using namespace std;
#define getmax(a,b) ((a)>(b)?(a):(b))
int main()
{
int x=5, y;
y= getmax(x,2);
cout << y << endl;
cout << getmax(7,x) << endl;
return 0;
}
Макрос #define не зависит от структуры блока. Макрос существует до тех пор, пока он не будет определен с помощью директивы препроцессора #undef:
#define TABLE_SIZE 1000
int table1[TABLE_SIZE];
#undef TABLE_SIZE
#define TABLE_SIZE 2000
int table2[TABLE_SIZE]
После обработки макроса будет получен следующий код:
int table1[1000];
int table2[2000];
Определения макросов функций допускают использование двух специальных операторов (#
и ##
) в последовательности замены. Если оператор #
используется до использования параметра в последовательности замены, этот параметр заменяется строковым литералом (как если бы он был заключен в двойные кавычки)
#define str(x) #x
cout << str(test);
что переводится как
cout << "test";
Однако ##
объединяет два аргумента, не оставляя между ними пробелов:
#define join(a,b) a ## b
join(c,out) << "test";
что переводится как
cout << "test";
Условные включения (#ifdef, #ifndef, #if, #endif, #else и #elif)
Эти директивы позволяют нам включать или исключать часть кода при соблюдении определенных условий.
#ifdef
позволяет компилировать часть программы только в том случае, если макрос, указанный в качестве параметра, был определен, независимо от его значения. Например
#ifdef TABLE_SIZE
int table[TABLE_SIZE];
#endif
В этом случае строка кода int table[TABLE_SIZE];
компилируется только в том случае, если TABLE_SIZE
был ранее определен с #define
, независимо от его значения. Если он не был определен, эта строка не будет включена в компиляцию программы.
#ifndef
служит прямо противоположной цели: код между директивами #ifndef
и #endif
компилируется только в том случае, если указанный идентификатор не был ранее определен. Например
#ifndef TABLE_SIZE
#define TABLE_SIZE 100
#endif
int table[TABLE_SIZE];
В этом случае, если элемент управления поступает в этот фрагмент кода, а макрос TABLE_SIZE
еще не определен, ему будет присвоено значение 100. Если он уже существует, он сохранит свое предыдущее значение, поскольку директива #define
не будет быть казненным.
Директивы #if
, #else
и #elif
(т. Е. «Else if») служат для определения некоторых условий, которые должны быть выполнены для компиляции той части кода, которую они окружают. Условие, которое следует за #if
или #elif
, может оценивать только константные выражения, включая макровыражения. Например:
#if TABLE_SIZE>200
#undef TABLE_SIZE
#define TABLE_SIZE 200
#elif TABLE_SIZE<50
#undef TABLE_SIZE
#define TABLE_SIZE 50
#else
#undef TABLE_SIZE
#define TABLE_SIZE 100
#endif
int table[TABLE_SIZE];
Обратите внимание, как связанные директивы #if
, #elif
и #else
заканчиваются на #endif
.
Поведение #ifdef
и #ifndef
также может быть достигнуто с помощью специальных операторов defined
и !defined
соответственно в любой из директив #if
или #elif
:
#if !defined TABLE_SIZE
#define TABLE_SIZE 100
#elif defined ARRAY_SIZE
#define TABLE_SIZE ARRAY_SIZE
int table[TABLE_SIZE];
#endif
Элемент управления строкой (#line)
Когда мы компилируем программу и в процессе компиляции возникают ошибки, компилятор показывает сообщения об ошибках со ссылками на имя файла, в котором произошла ошибка, и номер строки, поэтому легче найти код, генерирующий ошибку.
Директива #line
позволяет нам контролировать и то, и другое: номера строк в файлах кода, а также имя файла, которое мы хотим отображать при возникновении ошибки. Его формат:
#line number “filename”
где номер - это номер новой строки, который будет присвоен следующей строке кода. С этого момента номера следующих друг за другом строк будут увеличиваться одна за другой.
“filename”
- необязательный параметр, позволяющий переопределить имя файла, который будет отображаться. Например:
#include<iostream> int main() { #line 20 "assigning variable" int a?; }
Этот код сгенерирует ошибку, которая будет отображаться как ошибка
assigning variable: In function ‘int main()’: assigning variable:20:6: error: expected initializer before ‘?’ token
Er директива ror (#error)
Эта директива прерывает процесс компиляции, когда он обнаруживается, генерируя ошибку компиляции, которую можно указать в качестве ее параметра:
#ifndef __cplusplus
#error A C++ compiler is required!
#endif
Поскольку замена препроцессора происходит перед любой проверкой синтаксиса C ++, определения макросов - сложная функция. Код, основанный на сложных макросах, может показаться непонятным другим программистам, поскольку ожидаемый ими синтаксис может отличаться от стандартного соглашения, к которому привыкли программисты.