Директивы препроцессора в си
Директивы препроцессора
В C# определен ряд директив препроцессора, оказывающих влияние на интерпретацию исходного кода программы компилятором. Эти директивы определяют порядок интерпретации текста программы перед ее трансляцией в объектный код в том исходном файле, где они появляются. Термин директива препроцессора появился в связи с тем, что подобные инструкции по традиции обрабатывались на отдельной стадии компиляции, называемой препроцессором. Обрабатывать директивы на отдельной стадии препроцессора в современных компиляторах уже не нужно, но само ее название закрепилось.
Все директивы препроцессора начинаются со знака #. Кроме того, каждая директива препроцессора должна быть выделена в отдельную строку кода. Принимая во внимание современную объектно-ориентированную архитектуру языка C#, потребность в директивах препроцессора в нем не столь велика, как в языках программирования предыдущих поколений. Тем не менее они могут быть иногда полезными, особенно для условной компиляции. В этой статье все директивы препроцессора рассматриваются по очереди.
Директива #define
Директива #define определяет последовательность символов, называемую идентификатором. Присутствие или отсутствие идентификатора может быть определено с помощью директивы #if или #elif и поэтому используется для управления процессом компиляции. Ниже приведена общая форма директивы #define:
#define идентификатор
Обратите внимание на отсутствие точки с запятой в конце этого оператора. Между директивой #define и идентификатором может быть любое количество пробелов, но после самого идентификатора должен следовать только символ новой строки. Так, для определения идентификатора EXPERIMENTAL служит следующая директива:
#define EXPERIMENTAL
В C/C++ директива #define может использоваться для подстановки исходного текста, например для определения имени значения, а также для создания макрокоманд, похожих на функции. А в C# такое применение директивы #define не поддерживается. В этом языке директива #define служит только для определения идентификатора.
Директивы #if и #endif
Обе директивы, #if и #endif, допускают условную компиляцию последовательности кода в зависимости от истинного результата вычисления выражения, включающего в себя один или несколько идентификаторов. Идентификатор считается истинным, если он определен, а иначе - ложным. Так, если идентификатор определен директивой #define, то он будет оценен как истинный. Ниже приведена общая форма директивы #if:
#if идентификаторное_выражение последовательность операторов #endif
Если идентификаторное_выражение, следующее после директивы #if, истинно, то компилируется код (последовательность операторов), указываемый между ним и директивой #endif. В противном случае этот промежуточный код пропускается. Директива #endif обозначает конец блока директивы #if. Идентификаторное выражение может быть простым, как наименование идентификатора. В то же время в нем разрешается применение следующих операторов: !, ==, !=, && и ||, а также круглых скобок.
Ниже приведен пример применения упомянутых выше директив:
// Объявляем идентефикаторное выражение #define EXP #define TR using System; namespace ConsoleApplication1 { class Program { static void Main() { #if EXP Console.WriteLine("Проверка идентификатора EXP"); #endif #if EXP && TR Console.WriteLine("Проверка идентификатора EXP и TR"); #endif #if RES Console.WriteLine("Данный код не выполнится, т.к. такой идентификатор отсутствует"); #endif Console.ReadLine(); } } }
Директивы #else и #elif
Директива #else действует аналогично условному оператору else языка C#, определяя альтернативный ход выполнения программы, если этого не может сделать директива #if.
Обозначение #elif означает "иначе если", а сама директива #elif определяет последовательность условных операций if-else-if для многовариантной компиляции. После директивы #elif указывается идентификаторное выражение. Если это выражение истинно, то компилируется следующий далее кодовый блок, а остальные выражения директивы #elif не проверяются. В противном случае проверяется следующий по порядку блок. Если же ни одну из директив #elif не удается выполнить, то при наличии директивы #else выполняется последовательность кода, связанная с этой директивой, а иначе не компилируется ни один из кодовых блоков директивы #if.
Ниже приведена общая форма директивы #elif:
#if идентификаторное_выражение последовательность операторов #elif идентификаторное_выражение последовательность операторов #elif идентификаторное_выражение последовательность операторов //... #endif
Давайте добавим в предыдущий пример следующий код:
#if EXP Console.WriteLine("Проверка идентификатора EXP"); #endif #if EXP && TR Console.WriteLine("Проверка идентификатора EXP и TR"); #else Console.WriteLine("Данный код не выполнится"); #endif
Директива #undef
С помощью директивы #undef удаляется определенный ранее идентификатор. Это, по существу, означает, что он становится "неопределенным". Ниже приведена общая форма директивы #undef:
#undef идентификатор
Рассмотрим следующий пример кода
// Объявляем идентефикаторное выражение #define ID //удаляем идентификатор #undef ID using System; namespace ConsoleApplication1 { class Program { static void Main() { #if ID Console.WriteLine("Данное выражение не выполнится"); #endif Console.ReadLine(); } } }
Директива #undef применяется главным образом для локализации идентификаторов только в тех фрагментах кода, в которых они действительно требуются.
Директива #error
Директива #error вынуждает компилятор прервать компиляцию. Она служит в основном для отладки. Ниже приведена общая форма директивы #error:
#error сообщение_об_ошибке
Когда в коде встречается директива #error, выводится сообщение об ошибке. Например, когда компилятору встречается строка кода « #error Это тестовая ошибка! » компиляция прерывается и выводится сообщение "Это тестовая ошибка!".
Директива #warning
Директива #warning действует аналогично директиве #error, за исключением того, что она выводит предупреждение, а не ошибку. Следовательно, компиляция не прерывается. Ниже приведена общая форма директивы #warning:
#warning предупреждающее_сообщение
Директива #line
Директива #line задает номер строки и имя файла, содержащего эту директиву. Номер строки и имя файла используются при выводе ошибок или предупреждений во время компиляции. Ниже приведена общая форма директивы #line:
#line номер "имя_файла"
Имеются еще два варианта директивы #line. В первом из них она указывается с ключевым словом default, обозначающим возврат нумерации строк в исходное состояние, как в приведенном ниже примере:
#line default
А во втором варианте директива #line указывается с ключевым словом hidden. При пошаговой отладке программы строки кода, находящиеся между директивой #line hidden и следующей директивой #line без ключевого слова hidden, пропускаются отладчиком.
Препроцессор
- это специальная программа, являющаяся частью компилятора языка Си. Она предназначена для предварительной обработки текста программы. Препроцессор позволяет включать в текст программы файлы и вводить макроопределения.
Работа препроцессора осуществляется с помощью специальных директив (указаний). Они отмечаются знаком решетка #. По окончании строк, обозначающих директивы в языке Си, точку с запятой можно не ставить.
Основные директивы препроцессора
#include
- вставляет текст из указанного файла
#define
- задаёт макроопределение (макрос) или символическую константу
#undef
- отменяет предыдущее определение
#if
- осуществляет условную компиляцию при истинности константного выражения
#ifdef
- осуществляет условную компиляцию при определённости символической константы
#ifndef
- осуществляет условную компиляцию при неопределённости символической константы
#else
- ветка условной компиляции при ложности выражения
#elif
- ветка условной компиляции, образуемая слиянием else и if
#endif
- конец ветки условной компиляции
#line
- препроцессор изменяет номер текущей строки и имя компилируемого файла
#error
- выдача диагностического сообщения
#pragma
- действие, зависящее от конкретной реализации компилятора.
Директива #include
Директива #include
позволяет включать в текст программы указанный файл. Если файл является стандартной библиотекой и находится в папке компилятора, он заключается в угловые скобки <>
.
Если файл находится в текущем каталоге проекта, он указывается в кавычках ""
. Для файла, находящегося в другом каталоге необходимо в кавычках указать полный путь.
#include
#include
"func.c"
Директива #define
Директива #define
позволяет вводить в текст программы константы и макроопределения.
Общая форма записи
#define
Идентификатор Замена
Поля Идентификатор и Замена разделяются одним или несколькими пробелами.
Директива #define указывает компилятору, что нужно подставить строку, определенную аргументом Замена , вместо каждого аргумента Идентификатор в исходном файле. Идентификатор не заменяется, если он находится в комментарии, в строке или как часть более длинного идентификатора.
1
2
3
4
5
6
7
8
#include
#define
A 3
int
main()
{
printf("%d + %d = %d"
, A, A, A+A); // 3 + 3 = 6
getchar();
return
0;
}
В зависимости от значения константы компилятор присваивает ей тот или иной тип. С помощью суффиксов можно переопределить тип константы:
- U или u представляет целую константу в беззнаковой форме (unsigned );
- F (или f ) позволяет описать вещественную константу типа float ;
- L (или l ) позволяет выделить целой константе 8 байт (long int );
- L (или l ) позволяет описать вещественную константу типа long double
#define
A 280U // unsigned int
#define
B 280LU // unsigned long int
#define
C 280 // int (long int)
#define
D 280L // long int
#define
K 28.0 // double
#define
L 28.0F // float
#define
M 28.0L // long double
Вторая форма синтаксиса определяет макрос, подобный функции, с параметрами. Эта форма допускает использование необязательного списка параметров, которые должны находиться в скобках. После определения макроса каждое последующее вхождение
идентификатор(аргумент1, ..., агрументn)
замещается версией аргумента замена , в которой вместо формальных аргументов подставлены фактические аргументы.
Пример на Си
: Вычисление синуса угла
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include
#include
#include
#define
PI 3.14159265
#define
SIN(x) sin(PI*x/180)
int
main()
{
int
c;
system("chcp 1251"
);
system("cls"
);
printf("Введите угол в градусах: "
);
scanf("%d"
, &c);
printf("sin(%d)=%lf"
, c, SIN(c));
getchar(); getchar();
return
0;
}
Результат выполнения
Отличием таких макроопределений от функций в языке Си является то, что на этапе компиляции каждое вхождение идентификатора замещается соответствующим кодом. Таким образом, программа может иметь несколько копий одного и того же кода, соответствующего идентификатору. В случае работы с функциями программа будет содержать 1 экземпляр кода, реализующий указанную функцию, и каждый раз при обращении к функции ей будет передано управление.
Отменить макроопределение можно с помощью директивы #undef
.
Однако при использовании таких макроопределений следует соблюдать осторожность, например
1
2
3
4
5
6
7
8
9
10
11
12
13
#include
#define
sum(A,B) A+B
int
main()
{
int
a, b, c, d;
a = 3; b = 5;
getchar();
return
0;
}
Результат выполнения:
По умолчанию текст макроопределения должен размещаться на одной строке. Если требуется перенести текст макроопределения на новую строку, то в конце текущей строки ставится символ "обратный слеш" — \ .
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include
#define
sum(A,B) A + \
B
int
main()
{
int
a, b, c, d;
a = 3; b = 5;
c = (a + b) * 2; // c = (a + b)*2
d = sum(a, b) * 2; // d = a + b*2;
printf(" a = %d\n b = %d\n"
, a, b);
printf(" c = %d \n d = %d \n"
, c, d);
getchar();
return
0;
}
Кроме того, директива #define позволяет замещать часть идентификатора. Для указания замещаемой части используется ## .
1
2
3
4
5
6
7
8
9
#include
#define
SUM(x,y) (a##x + a##y)
int
main()
{
int
a1 = 5, a2 = 3;
printf("%d"
, SUM(1, 2)); // (a1 + a2)
getchar();
return
0;
}
Результат выполнения:
Директивы #if
или #ifdef/#ifndef
вместе с директивами #elif
, #else
и #endif
управляют компиляцией частей исходного файла.
Если указанное выражение после #if
имеет ненулевое значение, в записи преобразования сохраняется группа строк, следующая сразу за директивой #if
. Синтаксис условной директивы следующий:
1
2
3
4
5
6
7
#if
константное выражение
группа операций
#elif
константное выражение
группа операций
#else
группа операций
#endif
Отличие директив #ifdef/#ifndef заключается в том, что константное выражение может быть задано только с помощью #define .
У каждой директивы #if в исходном файле должна быть соответствующая закрывающая директива #endif . Между директивами #if и #endif может располагаться любое количество директив #elif , однако допускается не более одной директивы #else . Директива #else , если присутствует, должна быть последней перед директивой #endif .
Пример
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include
#include
#define
P 2
int
main()
{
system("chcp 1251"
);
system("cls"
);
#if
P==1
printf("Выполняется ветка 1"
);
#elif
P==2
printf("Выполняется ветка 2, P=%d"
, P);
#else
printf("Выполняется другая ветка, P=%d"
, P);
#endif
getchar();
return
0;
}
Препроцессор лучше всего рассматривать как отдельную программу, которая выполняется перед компиляцией. При запуске программы препроцессор просматривает код сверху вниз, файл за файлом, в поиске директив. Директивы — это специальные команды, которые начинаются с символа # и НЕ заканчиваются точкой с запятой. Есть несколько типов директив, которые мы рассмотрим ниже.
Директива #include
Вы уже видели директиву #include в действии. Когда вы #include файл, препроцессор копирует содержимое подключаемого файла в текущий файл сразу после строчки #include. Это очень полезно при использовании определённых данных (например, функций) сразу в нескольких местах.
Директива #include имеет две формы:
#include
#include "filename"
, которая сообщает препроцессору искать файл в текущей директории проекта. Если его там не окажется, то препроцессор начнёт проверять системные пути и любые другие, которые вы указали в настройках вашей . Эта форма используется для подключения пользовательских заголовочных файлов.
Директива #define
Директиву #define можно использовать для создания макросов. Макрос — это правило, которое определяет конвертацию идентификатора в указанные данные.
Есть два основных типа макросов: макросы-функции и макросы-объекты.
Макросы-функции ведут себя как функции и используются в тех же целях. Мы не будем сейчас их обсуждать, так как их использование, как правило, считается опасным, и почти всё, что они могут сделать, можно осуществить с помощью простой (линейной) функции.
Макросы-объекты можно определить одним из двух следующих способов:
#define identifier
#define identifier substitution_text
Верхнее определение не имеет никакого substitution_text , в то время как нижнее — имеет. Поскольку это директивы препроцессора (а не простые ), то ни одна из форм не заканчивается точкой с запятой.
Макросы-объекты с substitution_text
Когда препроцессор встречает макросы-объекты с substitution_text , то любое дальнейшее появление identifier заменяется на substitution_text . Идентификатор обычно пишется заглавными буквами с символами подчёркивания вместо пробелов.
Рассмотрим следующий фрагмент кода:
#define MY_FAVORITE_NUMBER 9 std::cout << "My favorite number is: " << MY_FAVORITE_NUMBER << std::endl;
#define MY_FAVORITE_NUMBER 9 std :: cout << "My favorite number is: " << MY_FAVORITE_NUMBER << std :: endl ; |
Препроцессор преобразует код выше в:
std::cout << "My favorite number is: " << 9 << std::endl;
Любое дальнейшее появление идентификатора USE_YEN удаляется и заменяется «ничем» (пустым местом)!
Это может показаться довольно бесполезным, однако, это не основное предназначение подобных директив. В отличие от макросов-объектов с substitution_text , эта форма макросов считается приемлемой для использования.
Условная компиляция
Директивы препроцессора условной компиляции позволяют определить, при каких условиях код будет компилироваться, а при каких — нет. В этом уроке мы рассмотрим только три директивы условной компиляции:
#ifdef;
#ifndef;
#endif.
Директива #ifdef (англ. «if def ined» = «если определено») позволяет препроцессору проверить, было ли значение ранее #define. Если да, то код между #ifdef и #endif скомпилируется. Если нет, то код будет проигнорирован. Например:
#define PRINT_JOE #ifdef PRINT_JOE std::cout << "Joe" << std::endl; #endif #ifdef PRINT_BOB std::cout << "Bob" << std::endl; #endif
#define PRINT_JOE #ifdef PRINT_JOE std :: cout << "Joe" << std :: endl ; #endif #ifdef PRINT_BOB std :: cout << "Bob" << std :: endl ; #endif |
Поскольку PRINT_JOE уже был #define, то строчка std::cout << "Joe" << std::endl; скомпилируется и выполнится. А поскольку PRINT_BOB не был #define, то строчка std::cout << "Bob" << std::endl; не скомпилируется и, следовательно, не выполнится.
Директива #ifndef (англ. «if n ot def ined» = «если не определено») - это полная противоположность #ifdef, которая позволяет проверить, не было ли значение ранее определено. Например:
#ifndef PRINT_BOB std::cout << "Bob" << std::endl; #endif
#ifndef PRINT_BOB std :: cout << "Bob" << std :: endl ; #endif |
Результатом выполнения этого фрагмента кода будет Bob , так как PRINT_BOB ранее никогда не был #define. Условная компиляция очень часто используется в качестве header guards (о них мы поговорим в следующем уроке).
Область видимости директивы #define
Директивы выполняются перед компиляцией программы: сверху вниз, файл за файлом.
Рассмотрим следующую программу:
#include
#include void boo () #define MY_NAME "Alex" int main () std :: cout << "My name is: " << MY_NAME ; return 0 ; |
Несмотря на то, что директива #define MY_NAME "Alex" определена внутри функции boo, препроцессор этого не заметит, так как он не понимает такие понятия C++, как функции. Следовательно, выполнение этой программы будет идентично той, в которой бы #define MY_NAME "Alex" было определено ДО, либо сразу ПОСЛЕ функции boo. Для лучше читабельности кода определяйте идентификаторы (с помощью #define) вне функций.
После того, как препроцессор завершит своё выполнение, все идентификаторы (определённые с помощью #define) из этого файла отбрасываются. Это означает, что директивы действительны только от точки определения до конца файла, в котором они определены. Директивы, определённые в одном файле кода, не влияют на директивы, определённые внутри других файлов этого же проекта.
Рассмотрим следующий пример:
#include
#include void doSomething () #ifdef PRINT std :: cout << "Printing!" ; #endif #ifndef PRINT std :: cout << "Not printing!" ; |
Директивы препроцессора языка си
Препроцессор языка Си – это программа, выполняющая обработку входных данных для другой программы. Препроцессор языка Си просматривает программу до компилятора, заменяет аббревиатуры в тексте программы на соответствующие директивы, отыскивает и подключает необходимые файлы, может влиять на условия компиляции. Директивы препроцессора не являются в действительности частью языка Си. Препроцессор включает в себя следующие директивы:
Определение |
Назначение |
Определение макроса |
|
Отмена определения макроса |
|
Включение объекта-заголовка |
|
Компиляция, если выражение истинно |
|
Компиляция, если макрос определен |
|
Компиляция, если макрос не определен |
|
Компиляция, если выражение в ifложно |
|
Составная директива else/if |
|
Окончание группы компиляции по условию |
|
Замена новым именем строки или имени исходного файла |
|
Формирование ошибок трансляции |
|
Действие определяется реализацией |
|
Null- директива |
Директива # define
Директива # define вводит макроопределение или макрос. Общая форма директивы следующая:
# define ИМЯ_МАКРОСА последовательность_символов
Последовательность символов называют еще строкой замещения. Когда препроцессор находит в исходном файле имя_макроса (просто макрос), он заменяет его на последовательность_символов.
Можно отменить определение макроса директивой # undef:
# undef имя_макроса
Данная строка удаляет любую ранее введенную строку замещения. Определение макроса теряется и имя_макроса становится неопределенным.
К примеру, можно определить МАХ как величину 100:
Это значение будет подставляться каждый раз вместо макроса МАХ в исходном файле, Можно также использовать макрос вместо строковой константы:
#defineNAME“TurboC++”
Если последовательность символов в директиве не помещается в одной строке, то можно поставить в конце строки \ и продолжить последовательность на другой строке. Среди программистов принято соглашение, что для имен макросов используются прописные буквы, так как их легко находить в программе. Также все директивы #defineлучше помещать в начало программы.
Директива # define имеет еще одну важную особенность: макрос может иметь аргументы. Каждый раз, когда происходит замена, аргументы заменяются на те, которые встречаются в программе.
Пример : #define MIN(a, b) ((9a)<(b)) ? (a) : (b)
printf(“Минимум из x и y “ % d, MIN(x ,y));
printf(“Минимум из a и b “ % d, MIN(n ,m));
Когда программа будет компилироваться, в выражение, определенное MIN(a,b) будут подставлены соответственноxиyилиmиn. Аргументыaиbзаключены в круглые скобки, так как вместо них может подставляться некоторое выражение, а не просто идентификатор.
Например, printf(“Минимум “ %d,MIN(x*x,x));
Директива # error
Имеет вид: # error сообщение_об_ошибке
Эта команда прекращает компиляцию программы и выдает сообщение об ошибке.
Директивы условной компиляции
К данным директивам относятся: # if , # else , # elif , # endif .
Данные директивы производят выборочную компиляцию программы. Если выражение, следующее за #if, истинно, то коды, заключенные между #ifи #endif, будут компилироваться. В противном случае они при компиляции будут пропущены. Выражение, следующее за #if, проверяется во время компиляции, поэтому оно может содержать только константы и макросы, которые прежде определены. Переменные здесь не могут использоваться.
Директива # else используется так же, как иelseв языке Си.
Пример: Использование условной компиляции.
# include
# define MAX 100
printf(“ MAX равно %d \n”, MAX);
Директива # elif используется для организации вложенной условной компиляции. Форма использования ее следующая:
#if<выражение>
последовательность операторов
#elif<выражение 1>
последовательность операторов
#elif<выражение 2>
последовательность операторов
…………………………………..
Другой метод условной компиляции состоит в использовании директив # ifdef и# ifndef . Основная форма использования этих директив следующая:
# ifdef ИМЯ_МАКРОСА
# endif
и соответственно
# ifndef ИМЯ_МАКРОСА
последовательность операторов
# endif
Если макрос определен, то при использовании # ifdefкомпилируется соответствующая последовательность до операторов #endif. Если же макрос не определен или был отменен директивой #undef, то соответствующая последовательность операторов игнорируется компилятором. Директива #ifndefдействует противоположным образом.
Заголовочные файлы включаются в текст программы с помощью директивы препроцессора
#include. Директивы препроцессора начинаются со знака "диез" (#),
который должен быть самым первым символом строки. Программа, которая обрабатывает
эти директивы, называется препроцессором
(в современных компиляторах
препроцессор обычно является частью самого компилятора).
Директива #include включает в программу содержимое указанного файла. Имя файла
может быть указано двумя способами:
#include
Если имя файла заключено в угловые скобки (<>), считается, что нам нужен
некий стандартный заголовочный файл, и компилятор ищет этот файл в предопределенных
местах. (Способ определения этих мест сильно различается для разных платформ
и реализаций.) Двойные кавычки означают, что заголовочный файл - пользовательский,
и его поиск начинается с того каталога, где находится исходный текст программы.
Заголовочный файл также может содержать директивы #include. Поэтому иногда трудно
понять, какие же конкретно заголовочные файлы включены в данный исходный текст,
и некоторые заголовочные файлы могут оказаться включенными несколько раз. Избежать
этого позволяют условные директивы препроцессора
. Рассмотрим пример:
#ifndef BOOKSTORE_H #define BOOKSTORE_H /* содержимое файла bookstore.h */ #endif
Условная директива #ifndef проверяет, не было ли значение BOOKSTORE_H определено
ранее. (BOOKSTORE_H - это константа препроцессора; такие константы принято писать
заглавными буквами.) Препроцессор обрабатывает следующие строки вплоть до директивы
#endif. В противном случае он пропускает строки от #ifndef до # endif.
Директива
#define BOOKSTORE_H
определяет константу препроцессора BOOKSTORE_H. Поместив эту директиву непосредственно
после директивы #ifndef, мы можем гарантировать, что содержательная часть заголовочного
файла bookstore.h будет включена в исходный текст только один раз, сколько бы
раз ни включался в текст сам этот файл.
Другим распространенным примером применения условных директив препроцессора
является включение в текст программы отладочной информации. Например:
Int main()
{
#ifdef DEBUG
cout << "Начало выполнения main()\n";
#endif
string word;
vector
Если константа DEBUG не определена, результирующий текст программы будет выглядеть так:
Int main()
{
string word;
vector
В противном случае мы получим:
Int main()
{
cout << "Начало выполнения main()\n";
string word;
vector
Константа препроцессора может быть определена в командной строке при вызове компилятора с помощью опции -D (в различных реализациях эта опция может называться по-разному). Для UNIX-систем вызов компилятора с определением препроцессорной константы DEBUG выглядит следующим образом:
$ CC -DDEBUG main.C
Есть константы, которые автоматически определяются компилятором. Например, мы можем узнать, компилируем ли мы С++ или С программу. Для С++ программы автоматически определяется константа __cplusplus (два подчеркивания). Для стандартного С определяется __STDC__. Естественно, обе константы не могут быть определены одновременно. Пример:
#idfef __cplusplus // компиляция С++ программы extern "C"; // extern "C" объясняется в главе 7 #endif int main(int,int);
Другими полезными предопределенными константами (в данном случае лучше сказать переменными) препроцессора являются __LINE__ и __FILE__. Переменная __LINE__ содержит номер текущей компилируемой строки, а __FILE__ - имя компилируемого файла. Вот пример их использования:
If (element_count == 0) cerr << "Ошибка. Файл: " << __FILE__ << " Строка: " << __LINE__ << "element_count не может быть 0";
Две константы __DATE__ и __TIME__ содержат дату и время компиляции.
Стандартная библиотека С предоставляет полезный макрос assert(), который проверяет
некоторое условие и в случае, если оно не выполняется, выдает диагностическое
сообщение и аварийно завершает программу. Мы будем часто пользоваться этим полезным
макросом в последующих примерах программ. Для его применения следует включить
в программу директиву
#include
assert.h - это заголовочный файл стандартной библиотеки С. Программа на C++
может ссылаться на заголовочный файл как по его имени, принятому в C, так и
по имени, принятому в C++. В стандартной библиотеке С++ этот файл носит имя
cassert. Имя заголовочного файла в библиотеке С++ отличается от имени соответствующего
файла для С отсутствием расширения.h и подставленной спереди буквой c (выше
уже упоминалось, что в заголовочных файлах для C++ расширения не употребляются,
поскольку они могут зависеть от реализации).
Эффект от использования директивы препроцессора #include зависит от типа заголовочного
файла. Инструкция
#include
включает в текст программы содержимое файла cassert. Но поскольку все имена, используемые в стандартной библиотеке С++, определены в пространстве std, имя assert() будет невидимо до тех пор, пока мы явно не сделаем его видимым с помощью следующей using-директивы:
Using namespace std;
Если же мы включаем в программу заголовочный файл для библиотеки С
#include
то надобность в using-директиве отпадает: имя assert() будет видно и так. (Пространства имен используются разработчиками библиотек для предотвращения засорения глобального пространства имен. В разделе 8.5 эта тема рассматривается более подробно.)