Директивы препроцессора — это строки, включаемые в код программы, которым предшествует символ решетки (#). Эти строки являются не выражениями программы, а директивами препроцессора. Препроцессор проверяет код до начала компиляции кода и заменяет все эти директивы на обычные выражения до того, как какой-либо код будет сгенерирован.
Директивы препроцессора распостраняются только на одну строку кода. Как только будет встречен симвод перевода строки, директива препроцессора завершится. Символ точки с запятой (;) не ожидается в конце директивы препроцессора. Единственный способ, которым директива препроцессора может быть расширена более, чем на одну строку, — это добавление символа обратного слеша (\) в конец строки перед символом перевода строки.
Определение макроса (#define, #undef)
Для определения макроса препроцессора можно использовать #define.
#define PI 3.14
Когда препроцессор встречает эту директиву, он заменяет каждое вхождение PI во всём остальном коде на 3.14 . В качестве замены можно использовать не только числа, но и выражения, блоки и что угодно еще. Препроцессор не понимает языка C++, он просто заменяет каждое вхождение идентификатора на другое значение, в данном примере заменяет PI на 3.14 .
#define TABLE_SIZE 100
int table1[TABLE_SIZE];
int table2[TABLE_SIZE];
После того, как препроцессор заменит TABLE_SIZE, код станет эквивалентен следующему:
int table1[100];
int table2[100];
#define также может работать с параметрами для определения макросов функций:
#define getmax(a,b) a>b?a:b
Это заменит каждое вхождение getmax с двумя аргументами указанным выражением, заменяя также каждый аргумент по его идентификатору так же, как если бы это была функция:
// макрос функции
#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;
}
5
7
Определения макросов не зависят от структуры блоков. Макрос длится до тех пор, пока не будет отменён директивой препроцессора #undef:
#define TABLE_SIZE 100
int table1[TABLE_SIZE];
#undef TABLE_SIZE
#define TABLE_SIZE 200
int table2[TABLE_SIZE];
Это сгенерирует код, который будет эквивалентен следующему:
int table1[100];
int table2[200];
Определения макросов функций могу принимать два специальных оператора (# и ##) внутри последовательности для замены:
Оператор #, за которым следует имя параметра, заменяется строковым литералом, содержащим переданный аргумент (аргумент заключается в двойные кавычки):
#define str(x) #x
cout << str(test);
транслируется в:
cout << "test";
Поскольку замены, производимые препроцессором, происходят до каких-либо проверок синтаксиса C++, макросы можно использовать для создания весьма хитроумных функций. Но будьте осторожны: код, зависящий от сложных макросов, становится менее читаемым, так как синтаксис при использовании макросов может сильно отличаться от обычных выражений, которые программист ожидает видеть в C++.
Условная компиляция (#ifdef, #ifndef, #if, #endif, #else and #elif)
Эти директивы позволяют включать или отбрасывать часть кода программы при выполнении определённого условия.
#ifdef позволяет скомпилировать часть кода, только если определён макрос, который указан как параметр, независимо от его значения. Например:
#ifdef TABLE_SIZE
int table[TABLE_SIZE];
#endif
В этом случае строка кода int table[TABLE_SIZE]; будет скомпилирована, только если макрос TABLE_SIZE был ранее определён посредством #define, независимо от значения TABLE_SIZE. Если макрос не был определён, эта строка кода будет проигнорирована при компиляции программы.
#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 ARRAY_SIZE
#define TABLE_SIZE ARRAY_SIZE
#elif !defined BUFFER_SIZE
#define TABLE_SIZE 128
#else
#define TABLE_SIZE BUFFER_SIZE
#endif
Управление строками (#line)
При возникновении некоторой ошибки в процессе компиляции программы компилятор показывает сообщение об ошибке со ссылкой на имя файла, в котором произошла ошибка, и номер строки, так что не проблема найти код, создающий ошибку.
Директива #line позволяет управлять и тем и другим: номерами строк в файлах с исходным кодом, а также именем файла, которое должно отображаться при возникновении ошибки. Её формат следующий:
#line number "filename"
Где number — это новый номер строки, который будет присвоен следующей строке кода. С этого момента номера последующих строк будут последовательно увеличиваться. "filename" — опциональный параметр, который позволяет переопределить отображаемое имя файла. Например:
#line 20 "assigning variable"
int a?;
Этот код создаст ошибку, которая будет отображена как error in file "assigning variable", line 20.
Директива #error
Эта директива прерывает процесс компиляции при ее обнаружении, генерируя ошибку компиляции, которая может быть указана в качестве параметра: