Директивы препроцессора — это строки, включаемые в код программы, которым предшествует символ решетки (#). Эти строки являются не выражениями программы, а директивами препроцессора. Препроцессор проверяет код до начала компиляции кода и заменяет все эти директивы на обычные выражения до того, как какой-либо код будет сгенерирован.
Директивы препроцессора распостраняются только на одну строку кода. Как только будет встречен символ перевода строки, директива препроцессора завершится. Символ точки с запятой (;) не ожидается в конце директивы препроцессора. Единственный способ, которым директива препроцессора может быть расширена более, чем на одну строку, — это добавление символа обратного слеша (\) в конец строки перед символом перевода строки.
Определение макроса (#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
Эта директива прерывает процесс компиляции при ее обнаружении, генерируя ошибку компиляции, которая может быть указана в качестве параметра:
#ifndef __cplusplus
#error A C++ compiler is required!
#endif
В этом примере процесс компиляции прерывается, если макрос __cplusplus не определён (этот макрос определён по умолчанию во всех компиляторах C++).
Включение исходных файлов (#include)
Эта директива активно используется в других разделах этого руководства. Когда препроцессор находит директиву #include, он замещает её всем содержимым указанного заголовка или файла. Есть два способа использовать #include:
#include <header>
#include "file"
В первом случае заголовок указывается в угловых скобках <>. Такой способ используется для включения заголовков, определяемых реализацией компилятора или библиотек, таких как заголовки, составляющие стандартную библиотеку (iostream,string,...).
Являются ли заголовки на самом деле файлами или существуют в какой-либо другой форме, определяется реализацией, но в любом случае они должны быть надлежащим образом включены этой директивой.
Синтаксис, используемый во втором #include использует двойные кавычки и включает file. Способ поиска file определяется реализацией компилятора, но обычно включает текущую директорию. В случае, если file не найден, компилятор интерпретирует его как включение заголовка, как будто двойные кавычки ("") заменены на угловые скобки (<>).
Директива #pragma
Эта директива используется для указания различных опций компилятору. Эти опции специфичны для платформы и компилятора. Необходимо ознакомиться с руководством или спецификацией на компилятор для дополнительной информации о возможных параметрах, которые можно определить с помощью #pragma.
Если компилятор не поддерживает какой-либо аргумент для #pragma, он игнорируется — синтаксическая ошибка не генерируется.
Предопределённые макросы
Следующие имена макросов всегда определены (все они начинаются и заканчиваются двумя подчёркиваниями, __):
Макрос | Значение |
__LINE__ | Целочисленное значение, равное номеру текущей строки в исходном коде |
__FILE__ | Строковый литерал, содержащий предполагаемое имя компилируемого исходного файла |
__DATE__ | Строковый литерал в формате «Ммм дд гггг», содержащий дату, в которую был начат процесс компиляции |
__TIME__ | Строковый литерал в формате «чч:мм:сс», содержащий время, когда был начат процесс компиляции |
__cplusplus | Целочисленное значение. Эта константа определена во всех компиляторах C++ некоторым значением. Это значение зависит от версии стандарта языка C++, поддерживаемого компилятором: 199711L : < C++11 201103L : C++11 201402L : C++14 201703L : C++17 202002L : C++20 202302L : C++23 Несоответствующие компиляторы определяют эту константу как некоторое значение длиной не более пяти цифр. Обратите внимание, что многие компиляторы не полностью соответствуют требованиям и могут не определять эту константу ни как одно из указанных выше значений. |
__STDC_HOSTED__ | Расширяется в целочисленную константу 1, если реализация выполняется из операционной системы (со всеми доступными стандартными заголовками), 0 — если выполняется без операционной системы. |
Следующие макросы определены опционально, обычно в зависимости от доступной функциональности:
Макрос | Значение |
__STDC__ | в C: если значение равно 1, то реализация соответствует стандарту C в C++: зависит от реализации |
__STDC_VERSION__ | в C: расширяется в целочисленную константу типа long, значение которого увеличивается с каждой версией стандарта C 199409L: C95 199901L: C99 201112L: C11 201710L: C17 202311L: C23 в C++: зависит от реализации |
__STDC_MB_MIGHT_NEQ_WC__ | 1 если многобайтовая кодировка может задать символу другое значение в символьных литералах |
__STDC_ISO_10646__ | Значение в форме ггггммL, указывающее дату применения стандарта Unicode, за которым следует кодировка символов wchar_t |
__STDCPP_STRICT_POINTER_SAFETY__ | 1 если реализация имеет строгую безопасность указателя (см. get_pointer_safety) |
__STDCPP_THREADS__ | 1 если в программе может быть более одного потока |
Конкретная реализация может определять дополнительные константы.
Например:
// standard macro names
#include <iostream>
using namespace std;
int main()
{
cout << "This is the line number " << __LINE__;
cout << " of file " << __FILE__ << ".\n";
cout << "Its compilation began " << __DATE__;
cout << " at " << __TIME__ << ".\n";
cout << "The compiler gives a __cplusplus value of " << __cplusplus;
return 0;
}
This is the line number 7 of file /home/jay/stdmacronames.cpp.
Its compilation began Nov 1 2005 at 10:12:29.
The compiler gives a __cplusplus value of 1
.