Сайт о телевидении

Сайт о телевидении

» » Директивы препроцессора. Препроцессорные директивы

Директивы препроцессора. Препроцессорные директивы

сочетания символов (символические аббревиатуры) на соответствующие директивы. Он отыскивает и подключает к программе необходимые файлы и может изменить условия компиляции [19.1 ]. Препроцессор имеет тот же смысл, что и буферный процессор .

Директива #define

Директива #define определяет идентификатор и последовательность символов, которая будет подставляться вместо идентификатора каждый раз, когда он встретится в исходном файле. Идентификатор называется именем макроса, а сам процесс замены – макрозаменой (макрорасширением, макрогенерацией, макроподстановкой) [19.3 ]. В общем виде директива #define выглядит следующим образом (должно быть задано буквами латинского алфавита):

#define имя_макроса последовательность_символов

В определении, как правило, в конце последовательности символов не ставится точки с запятой. Между идентификатором и последовательностью символов последовательность_символов может быть любое количество пробелов, но признаком конца последовательности символов может быть только разделитель строк [19.3 ].

Имена макросов обычно задаются с помощью букв верхнего регистра.

У директивы #define имя макроса может определяться с формальными параметрами. Тогда каждый раз, когда в программе встречается имя макроса, то используемые в его определении формальные параметры заменяются теми аргументами, которые встретились в программе. Такого рода макросы называются макросами с формальными параметрами (макроопределениями с параметрами и макросами, напоминающими функции ) [19.3 ]. Ключевым элементом макроса с параметрами являются круглые скобки, внутри которых находятся собственно формальные параметры. Рассмотрим пример макроса с тремя параметрами для определения следующего условия: будет ли остаток от деления случайной функции на правую границу интервала больше, чем половина этого интервала.

Программный код решения примера

#include #include #include #include #define MAX(a,b,c) ((1+rand()%(b)) > ((a)+(b))/2) ? (c):(b) int main (void) { int a, b, c; srand((unsigned) time(NULL)); printf("\n Enter a, b, c: "); scanf_s("%d%d%d", &a, &b, &c); printf("\n MAX(a,b,c): %d\n", MAX(a,b,c)); printf("\n\n ... Press any key: "); _getch(); return 0; }

Использование вместо настоящих функций макросов с формальными параметрами (т. е. a, b, с ) дает следующее преимущество: увеличивается скорость выполнения кода, потому что в таких случаях не надо тратить ресурсы на вызов функций. Кроме того, макрос не чувствителен к типу данных, т. е. в нем отсутствует проверка типов аргументов. Однако если у макроса с формальными параметрами очень большие размеры, то тогда из-за дублирования кода увеличение скорости достигается за счет увеличения размеров программы [19.3 ]. И еще, в конструировании макроса следует быть очень внимательным. Как правило, макросы используются для небольших пользовательских функций [19.4 ].

Директива #error

Директива #error заставляет компилятор прекратить компиляцию [19.3 ]. Эта директива используется в основном для отладки. В общем виде директива #error выглядит следующим образом:

#error сообщение – об – ошибке

Заданное сообщение об ошибке (сообщение – об – ошибке ) в двойные кавычки не заключается. Когда встречается данная директива, то выводится сообщение об ошибке – возможно, вместе с другой информацией, определяемой компилятором [19.3 ].

Директива #include

Директива #include дает указание компилятору читать еще один исходный файл – в дополнение к тому файлу, в котором находится сама эта директива [19.3 ]. Имя исходного файла (подключаемого файла) должно быть заключено в двойные кавычки или в угловые скобки. Обычно имена стандартных заголовочных файлов заключают в угловые скобки. А использование кавычек обычно приберегается для имен специальных файлов, относящихся к конкретной программе. Способ поиска файла зависит от того, заключено ли его имя в двойные кавычки или же в угловые скобки. Если имя заключено в угловые скобки, то поиск файла проводится тем способом, который определен в компиляторе. Часто это означает поиск определенного каталога, специально предназначенного для хранения таких файлов. Если имя заключено в кавычки, то поиск файла проводится другим способом. Во многих компиляторах это означает поиск файла в текущем рабочем каталоге. Если же файл не найден, то поиск повторяется уже так, как будто имя файла заключено в угловые скобки [19.3 ].

Файлы, имена которых находятся в директивах #include, могут в свою очередь содержать другие директивы #include. Они называются вложенными директивами #include. Количество допустимых уровней вложенности у разных компиляторов может быть разным. Однако в стандарте С89 предусмотрено, что компиляторы должны допускать не менее 8 таких уровней [19.3 ].

Директивы условной компиляции

Каждая директива #if сопровождается директивой #endif.

В общем случае директива #undef выглядит следующим образом:

#undef имя_макроса

Директива #undef используется в основном для того, чтобы локализовать имена макросов в тех участках кода, где они нужны.

Для того чтобы узнать определено ли имя макроса, можно использовать директиву #if в сочетании с оператором времени компиляции defined [19.3 ].

Директива #line

Директива #line изменяет содержимое __LINE__ и __FILE__, которые являются зарезервированными идентификаторами (макросами) в компиляторе. В первом из них содержится номер компилируемой в данный момент строки программного кода программы [

Введение

Препроцессор языка Си представляет собой макропроцессор, используемый для обработки исходного файла на нулевой фазе компиляции. Компилятор языка Си сам вызывает препроцессор, однако препроцессор может быть вызван и автономно. Директивы препроцессора представляют собой инструкции, записанные в исходном тексте программы на языке Си и предназначенные для выполнения препроцессором языка Си.

Директивы препроцессора обычно используются для того, чтобы облегчить модификацию исходных программ и сделать их более независимыми от особенностей различных реализаций компилятора языка Си, разных компьютеров и операционных сред. Директивы препроцессора позволяют заменить лексемы в тексте программы некоторыми значениями, вставить в исходный файл содержимое другого исходного файла, запретить компиляцию некоторой части исходного файла и т.д. Препроцессор Си распознает следующие директивы:

#define #else #if #ifndef #line
#elif #endif #ifdef #include #undef

Символ # должен быть первым в строке, содержащей директиву в СП MSC версии 4. В СП MSC версии 5 ив СП ТС ему могут предшествовать пробельные символы. Как в СП MSC, так и в СП ТС пробельные символы допускаются между символом # и первой буквой директивы.

Некоторые директивы могут содержать аргументы. Директивы могут быть записаны в любом месте исходного файла, но их действие распространяется только от точки программы, в которой они записаны, до конца исходного файла.

Указания компилятору, или прагмы, представляют собой инструкции, записываемые в исходном тексте программы и предназначенные для управления действиями компилятора языка Си в определенных ситуациях. Набор указаний компилятору и их смысл различаются для разных компиляторов языка Си, поэтому в разделе 7.8 описывается только общий синтаксис указаний компилятору.

В рассматриваемых системах программирования есть возможность получить промежуточный текст программы после работы препроцессора, до начала собственно компиляции. В этом файле уже выполнены макроподстановки, а все строки, содержащие директивы #define и #undef, заменены на пустые строки. На место строк #include подставлено содержимое соответствующих включаемых файлов. Выполнена обработка директив условной компиляции #if, #elif, #else, #ifdef, #ifndef, #endif, а строки, содержащие их, заменены пустыми строками. Пустыми строками заменены и исключенные в процессе условной компиляции фрагменты исходного текста. Кроме того, в этом файле есть строки следующего вида:

#["имя файла"]

которые соответствуют точкам изменения номера текущей строки и/или номера файла по директивам #line или #include.

Именованные константы и макроопределения

Директива #define обычно используется для замены часто используемых в программе констант, ключевых слов, операторов и выражений осмысленными идентификаторами. Идентификаторы, которые заменяют числовые или текстовые константы либо произвольную последовательность символов, называются именованными константами. Идентификаторы, которые представляют некоторую последовательность действий, заданную операторами или выражениями языка Си, называются макроопределениями. Макроопределения могут иметь аргументы. Обращение к макроопределению в программе называется макровызовом.

В языке Си принято записывать идентификаторы именованных констант и макроопределений символами верхнего регистра, чтобы отличать их от имен переменных и функций. Это, однако, не является требованием языка Си.

Директива #undef отменяет текущее определение именованной константы. Только когда определение отменено, именованной константе может быть сопоставлено другое значение. Однако многократное повторение определения с одним и тем же значением не считается ошибкой.

Макроопределение напоминает по синтаксису определение функции. Однако замена вызова функции макровызовом может повысить скорость выполнения программы, поскольку для макроопределения не требуется генерировать вызывающую последовательность, которая занимает относительно большое время (засылка аргументов в стек, передача управления и т.п.). С другой стороны, многократное употребление макроопределения в программе может потребовать значительно большей памяти, чем вызовы функции (код для функции генерируется один раз, а для макроопределения - столько раз, сколько имеется макровызовов в программе).

Имеется возможность задавать определения именованных констант не только в исходном тексте, но и в командной строке компиляции.

Имеется ряд предопределенных идентификаторов, которые нельзя использовать в директивах #define и #undef в качестве идентификаторов. Они рассмотрены в разделе 7.9 "Псевдопеременные".

Директива #define

Синтаксис:

Директива #define заменяет все вхождения в исходном файле на, следующий в директиве за. Этот процесс называется макроподстановкой, заменяется лишь в том случае, если он представляет собой отдельную лексему. Например, если является частью строки или более длинного идентификатора, он не заменяется. Если за следует, то директива определяет макроопределение с аргументами.

представляет собой набор лексем, таких как ключевые слова, константы, идентификаторы или выражения. Один или более пробельных символов должны отделять от (или от заключенных в скобки параметров). Если текст не умещается на строке, то он может быть продолжен на следующей строке; для этого следует набрать в конце строки символ обратный слэш и сразу за ним нажать клавишу ENTER.

может быть опущен. В этом случае все экземпляры будут удалены из исходного текста программы. Тем не менее, сам рассматривается как определенный и при проверке директивой #if дает значение 1 (смотри раздел 7.4.1).

Если он задан, содержит один или более идентификаторов, разделенных запятыми. Идентификаторы в списке должны отличаться друг от друга. Их область действия ограничена макроопределением, в котором они заданы. Список должен быть заключен в круглые скобки. Имена формальных параметров в отмечают позиции, в которые должны быть подставлены фактические аргументы макровызова. Каждое имя формального параметра может появиться в произвольное число раз.

В макровызове следом за записывается в круглых скобках список фактических аргументов, соответствующих формальным параметрам из. модифицируется путем замены каждого формального параметра на соответствующий фактический аргумент. Списки фактических аргументов и формальных параметров должны содержать одно и то же число элементов.

Примечание. Не следует путать подстановку аргументов в макроопределение с передачей аргументов функции. Подстановка в препроцессоре носит чисто текстовый характер. Никаких вычислений или преобразований типа при этом не производится.

Выше уже говорилось, что макроопределение может содержать более одного вхождения данного формального параметра. Если формальный параметр представлен выражением с побочным эффектом, то это выражение будет вычисляться более одного раза, а вместе с ним каждый раз будет возникать и побочный эффект. Результат выполнения в этом случае может быть ошибочным.

Внутрь в директиве #define могут быть вложены имена других макроопределений или констант. Их расширение производится лишь при расширении этого, а не при его определении директивой #define. Это надо учитывать, в частности, при взаимодействии вложенных именованных констант и макроопределений с директивой #undef: к моменту расширения содержащего их текста они могут уже оказаться отменены директивой #undef.

После того как выполнена макроподстановка, полученная строка вновь просматривается для поиска других имен констант и макроопределений. При повторном просмотре не принимается к рассмотрению имя ранее произведенной макроподстановки. Поэтому директива

не приведет к зацикливанию препроцессора.

/* пример 1 */

#define WIDTH 80

#define LENGTH (WIDTH + 10)

/* пример 2 */

#define FILEMESSAGE "Попытка создать файл\

не удалась из-за нехватки дискового пространства"

/* пример 3 */

#define REG1 register

#define REG2 register

/* пример 4 */

#define MAX(x, y)((x)>(у)) ? (x) : (у)

/* пример 5 */

#define MULT(a, b) ((a)*(b))

В первом примере идентификатор WIDTH определяется как целая константа со значением 80, а идентификатор LENGTH - как текст (WIDTH + 10). Каждое вхождение идентификатора LENGTH в исходный файл будет заменено на текст (WIDTH + 10), который после расширения идентификатора WIDTH превратится в выражение (80 + 10). Скобки, окружающие текст (WIDTH + 10), позволяют избежать ошибок в операторах, подобных следующему:

var = LENGTH * 20;

После обработки препроцессором оператор примет вид:

var = (80 + 10)* 20;

Значение, которое присваивается var, равно 1800. В отсутствие скобок в макроопределении оператор имел бы следующий вид:

var = 80 + 10*20;

Значение var равнялось бы 280, поскольку операция умножения имеет более высокий приоритет, чем операция сложения.

Во втором примере определяется идентификатор FILEMESSAGE. Его определение продолжается на вторую строку путем использования символа обратный слэш непосредственно перед нажатием клавиши ENTER.

В третьем примере определены три идентификатора, REG1, REG2, REG3. Идентификаторы REG1 и REG2 определены как ключевые слова register. Определение REG3 опущено и, таким образом, любое вхождение REG3 будет удалено из исходного файла. В разделе 7.4.1 приведен пример, показывающий, как эти директивы могут быть использованы для задания класса памяти register наиболее важным переменным программы.

В четвертом примере определяется макроопределение МАХ. Каждое вхождение идентификатора МАХ в исходном файле заменяется на выражение ((x)>(у))?(x):(у), в котором вместо формальных параметров х и у подставлены фактические. Например, макровызов

заменится на выражение

((1)>(2))?(1):(2)

а макровызов

заменится на выражение

((i)>(s(i]))?(i):(s(i])

Обратите внимание на то, что в этом макроопределении аргументы с побочными эффектами могут привести к неверным результатам. Например, макровызов

заменится на выражение

((i)>(s))?(i):(s)

Операнды операции > могут быть вычислены в любом порядке, а значение переменной i зависит от порядка вычисления. Поэтому результат выражения непредсказуем. Кроме того, возможна ситуация, когда переменная i будет инкрементирована дважды, что, вероятно, не требуется.

В пятом примере определяется макроопределение MULT. Макровызов MULT(3,5) в тексте программы заменяется на (3)*(5). Круглые скобки, в которые заключаются фактические аргументы, необходимы в тех случаях, когда аргументы макроопределения являются сложными выражениями. Например, макровызов

заменится на (3+4)*(5+6), что равняется 76. В отсутствие скобок результат подстановки 3+4*5+6 был бы равен 29.

Склейка лексем и преобразование аргументов макроопределений

СП ТС и версия 5.0 СП MSC реализуют две специальные препроцессорные операции: ## и #.

В директиве #define две лексемы могут быть "склеены" вместе. Для этого их нужно разделить знаками ## (слева и справа от ## допустимы пробельные символы). Препроцессор объединяет такие лексемы в одну; например, макроопределение

#define VAR (i, j) i##j

при макровызове VAR(х,6) образует идентификатор х6. Некоторые компиляторы позволяют в аналогичных целях употребить запись х/**/6, но этот метод менее переносим.

Символ #, помещаемый перед аргументом макроопределения, указывает на необходимость преобразования его в символьную строку. При макровызове конструкция # заменяется на "".

Пример: макроопределение TRACE позволяет печатать с помощью стандартной функции printf значения переменных типа int в формате = .

#define TRACE(flag) printf (#flag " = %d\n", flag)

Следующий фрагмент текста программы:

TRACE (highval);

примет после обработки препроцессором вид:

printf("highval" " = %d\n", highval);

Следующие друг за другом символьные строки рассматриваются компилятором языка Си в СП MSC версии 5 и в СП ТС как одна строка, поэтому полученная запись эквивалентна следующей:

printf("highval = %d\n", highval);

При макровызове сначала выполняется макроподстановка всех аргументов макровызова, а затем их подстановка в тело макроопределения. Поэтому следующая программа напечатает строку "отклонение от стандарта":

#define АВ "стандарт"

#define А "отклонение"

#define В "от стандарта"

#define CONCAT(P,Q) Р##Q

printf(CONCAT(A,В) "\n");

Директива #undef

Синтаксис:

Директива #undef отменяет действие текущего определения #define для. Чтобы отменить макроопределение посредством директивы #undef, достаточно задать его. Задание списка параметров не требуется.

Не является ошибкой применение директивы #undef к идентификатору, который ранее не был определен (или действие его определения уже отменено). Это может использоваться для гарантии того, что идентификатор не определен.

Директива #undef обычно используется в паре с директивой #define, чтобы создать область исходной программы, в которой некоторый идентификатор определен.

#define WIDTH 80

#define ADD(X, Y) (X)+(Y)

В этом примере директива #undef отменяет определение именованной константы WIDTH и макроопределения ADD. Обратите внимание на то, что для отмены макроопределения задается только его идентификатор.

Включение файлов

Синтаксис:

#include "имя пути"

Директива #include включает содержимое исходного файла, которого задано, в текущий компилируемый исходный файл. Например, общие для нескольких исходных файлов определения именованных констант и макроопределения могут быть собраны в одном включаемом файле и включены директивой #include во все исходные файлы. Включаемые файлы используются также для хранения объявлений внешних переменных и абстрактных типов данных, разделяемых несколькими исходными файлами.

Препроцессор обрабатывает включаемый файл таким же образом, как если бы этот файл целиком входил в состав исходного файла в точке, где записана директива #include. Включаемый текст также может содержать директивы препроцессора. Препроцессор выполняет обработку включаемого файла, а затем возвращается к обработке первоначального исходного файла.

Имя пути представляет собой имя файла, которому может предшествовать имя устройства и спецификация директории. Синтаксис имени пути определяется соглашениями операционной системы.

Препроцессор использует понятие стандартных директорий для поиска включаемых файлов. Стандартные директории задаются командой PATH операционной системы.

Препроцессор ведет поиск до тех пор, пока не обнаружит файл с заданным именем.

Если имя пути задано однозначно (полностью) и заключено в двойные кавычки, то препроцессор ищет файл только в директории, специфицированной заданным именем пути, а стандартные директории игнорирует.

Если заданная в кавычках спецификация не образует полное имя пути, то препроцессор начинает поиск включаемого файла в текущей рабочей директории (т. е. в той директории, которая содержит исходный файл, в котором записана директива #include).

Директива #include может быть вложенной. Это значит, что она может встретиться в файле, включенном другой директивой #include. Когда препроцессор обнаруживает вложенную директиву #include, он начинает поиск файла в текущей директории, соответствующей исходному файлу, который содержит эту вложенную директиву #include. После этого препроцессор переходит к поиску в текущей директории, соответствующей охватывающему исходному файлу, т.е. тому, по отношению к которому данная директива #include является вложенной. Допустимый уровень вложенности директив #include зависит от реализации компилятора. Процесс поиска в охватывающих директориях продолжается до тех пор, пока не будет просмотрена текущая директория самого первого исходного файла, т. е. файла, имя которого было задано при вызове компилятора языка Си.

Затем препроцессор продолжает поиск в директориях, указанных в командной строке компиляции, и, наконец, ищет в стандартных директориях.

Если же имя пути заключено в угловые скобки, то препроцессор вообще не будет осуществлять поиск в текущей рабочей директории, а сразу начнет поиск в директориях, специфицированных в командной строке компиляции, а затем в стандартных директориях.

#include /* пример 1 */

#include "defs.h" /* пример 2 */

В первом примере в исходный файл включается файл с именем stdio.h. Угловые скобки сообщают препроцессору, что поиск файла нужно осуществлять в директории, указанной в командной строке компиляции, а затем в стандартных директориях.

Во втором примере в исходный файл включается файл с именем defs.h. Двойные кавычки означают, что при поиске файла сначала должна быть просмотрена директория, содержащая текущий исходный файл.

В СП ТС имеется возможность задавать имя пути в директиве #include с помощью именованной константы. Если за словом include следует идентификатор, препроцессор проверяет, не именует ли он константу или макроопределение. Если же за словом include следует строка, заключенная в кавычки или в угловые скобки, СП ТС не будет искать в ней имя константы.

#define myinclude "c:\tc\include\mystuff.h"

#include myinclude

#include "myinclude.h"

Первая директива #include заставит препроцессор просматривать директорию C:\TC\INCLUDE\MYSTUFF.H, а вторая заставит искать файл MYINCLUDE.H в текущей директории.

Объединение символьных строк и склейку лексем в именованной константе, которая используется в директиве #include, использовать нельзя. Результат расширения константы должен сразу читаться как корректная директива #include.

Условная компиляция

В этом разделе описываются директивы, которые управляют условной компиляцией. Эти директивы позволяют исключить из процесса компиляции какие-либо части исходного файла посредством проверки условий (константных выражений).

Директивы #if, #elif, #else, #endif

Синтаксис:

Директива #if совместно с директивами #elif, #else и #endif управляет компиляцией частей исходного файла. Каждой директиве #if в том же исходном файле должна соответствовать завершающая ее директива #endif. Между директивами #if и #endif допускается произвольное количество директив #elif (в том числе ни одной) и не более одной директивы #else. Если директива #else присутствует, то между ней и директивой #endif на данном уровне вложенности не должно быть других директив #elif.

Препроцессор выбирает один из участков для обработки. может занимать более одной строки. Обычно это участок программного текста, однако это не обязательно: препроцессор можно использовать для обработки произвольного текста. Если содержит директивы препроцессора (в том числе и директивы условной компиляции), то эти директивы выполняются. Обработанный препроцессором текст передается на компиляцию.

Участок текста, не выбранный препроцессором, игнорируется на стадии препроцессорной обработки и не компилируется.

Препроцессор выбирает участок текста для обработки на основе вычисления, следующего за каждой директивой #if или #elif. Выбирается, следующий за со значением истина (не нуль), вплоть до ближайшей директивы #elif, #else, или #endif, ассоциированной с данной директивой #if.

Если ни одно ограниченное константное выражение не истинно, то препроцессор выбирает, следующий за директивой #else. Если же директива #else отсутствует, то никакой текст не выбирается.

Ограниченное константное выражение описано в разделе 4.2.9 "Константные выражения". Такое выражение не может содержать операцию sizeof (в СП ТС - может), операцию приведения типа, константы перечисления и плавающие константы, но может содержать препроцессорную операцию defined(). Эта операция дает истинное (не равное нулю) значение, если заданный в данный момент определен; в противном случае выражение ложно (равно нулю). Следует помнить, что идентификатор, определенный без значения, тем не менее рассматривается как определенный. Операция defined может использоваться в сложном выражении в директиве #if неоднократно:

#if defined(mysym) || defined(yoursym)

СП TC (в отличие от СП MSC) позволяет использовать операцию sizeof в ограниченном константном выражении для препроцессора. В следующем примере в зависимости от размера указателя определяется одна из констант - либо SDATA, либо LDATA:

#if (sizeof(void *) == 2)

Директивы #if могут быть вложенными. При этом каждая из директив #else, #elif, #endif ассоциируется с ближайшей предшествующей директивой #if.

/* пример 1 */

#if defined(CREDIT)

#elif defined (DEBIT)

/* пример 2 */

#define SIGNAL 1

#if STACKUSE == 1

#derine STACK 200

#define STACK 100

#define SIGNAL 0

#if STACKUSE == 1

#define STACK 100

#define STACK 50

/* пример 3 */

#elif DLEVEL == 1

#define STACK 100

#elif DLEVEL > 5

display(debugptr);

#define STACK 200

/* пример 4 */

#define REG 1 register

#define REG2 register

#if defined (M_86)

#ifdefined(M_68000)

#define REG4 register

В первом примере директивы #if, #elif, #else, #endif управляют компиляцией одного из трех вызовов функции. Вызов функции credit компилируется, если определена именованная константа CREDIT. Если определена именованная константа DEBIT, то компилируется вызов функции debit. Если ни одна из.именованных констант не определена, то компилируется вызов функции printerror. Следует учитывать, что CREDIT и credit являются различными идентификаторами в языке Си.

В следующих двух примерах предполагается, что константа DLEVEL предварительно определена директивой #define.

Во втором примере показаны два вложенных набора директив #if, #else, #endif. Первый набор директив обрабатывается, если значение DLEVEL больше 5. В противном случае обрабатывается второй набор.

В третьем примере директивы уловной компиляции используют для выбора текста значение константы DLEVEL. Константа STACK определяется со значением 0, 100 или 200, в зависимости от значения DLEVEL. Если DLEVEL больше 5, то компилируется вызов функции display, а константа STACK не определяется.

В четвертом примере директивы препроцессора используются для контроля за применением спецификации регистрового класса памяти в программе, предназначенной для работы в различных операционных средах.

Компилятор обычно выделяет регистровую память переменным в том порядке, в котором записаны объявления переменных в программе. Если программа содержит больше объявлений переменных класса памяти register, чем имеется регистров в данной операционной среде, то регистровую память получат только те переменные, объявления которых записаны раньше. Следовательно, если более интенсивно будут использоваться те переменные, которые объявлены позже, выигрыш в эффективности от использования регистров окажется незначительным.

В примере показано, каким образом предоставить приоритет регистровой памяти наиболее важным переменным. Именованные константы REG1 и REG2 определяются как ключевые слова register. Они предназначены для объявления двух наиболее важных локальных переменных функции. Например, в следующем фрагменте программы такими переменными являются b и c.

func(REG3 int а)

Если определена константа М_86, препроцессор удаляет идентификаторы REG3 и REG4 из файла путем замены их на пустой текст. Регистровую память в этом случае получат только переменные b и с. Если определен идентификатор М_68000, то все четыре переменные объявляются с классом памяти register.

Если не определена ни одна из констант - ни М_86, ни М_68000, - то регистровую память получат переменные а, b и с.

Директивы #ifdef и #ifndef

Синтаксис:

Аналогично директиве #if, за директивами #ifdef и #ifndef может следовать набор директив #elif и директива #else. Набор должен быть завершен директивой #endif.

Использование директив #ifdef и #ifndef эквивалентно применению директивы #if, использующей выражение с операцией defined(). Эти директивы поддерживаются исключительно для совместимости с предыдущими версиями компиляторов языка Си. Для новых программ рекомендуется использовать директиву #if с операцией defined().

Когда препроцессор обрабатывает директиву #ifdef, он проверяет, определен ли в данный момент директивой #define. Если да, условие считается истинным, если нет - ложным.

Директива #line обычно используется автоматическими генераторами программ для того, чтобы диагностические сообщения относились не к исходному файлу, а к сгенерированной программе.

в директиве #line может быть произвольной целой константой. может быть произвольной комбинацией символов, заключенной в двойные кавычки. Если имя файла опущено, то имя исходного файла остается прежним.

Текущий номер строки и имя исходного файла доступны в программе через псевдопеременные с именами __LINE__ и __FILE__. Эти псевдопеременные могут быть использованы для выдачи во время выполнения сообщений о точном местоположении ошибки.

Значением псевдопеременной __FILE__ является строка, представляющая имя файла, заключенное в двойные кавычки. Поэтому для печати имени исходного файла не требуется заключать сам идентификатор __FILE__ в двойные кавычки.

/* пример 1 */

#line 151 "copy.с"

/* пример 2 */

#define ASSERT(cond) if (!cond)\

{printf ("ошибка в строке %d файла %s\n", \

LINE__, __FILE__);} else;

В первом примере устанавливается имя исходного файла сору.с и текущий номер строки 151.

Во втором примере в макроопределении ASSERT используются псевдопеременные __LINE__ и __FILE__ для печати сообщения об ошибке, содержащего координаты исходного файла, если некоторое условие, заданное макроаргументом cond, ложно.

Директива обработки ошибок

В СП ТС реализована директива #error. Ее формат:

Обычно эту директиву записывают среди директив условной компиляции для обнаружения некоторой недопустимой ситуации. По директиве #error препроцессор прерывает компиляцию и выдает следующее сообщение:

Fatal: Error directive:

Fatal - признак фатальной ошибки; - имя исходного файла; - текущий номер строки; Error directive - сообщение об ошибке в директиве; - собственно текст диагностического сообщения.

Указания компилятору языка Си

Синтаксис:

Указания компилятору, или прагмы, предназначены для исполнения компилятором в процессе его работы. задает определенную инструкцию компилятору и, возможно, аргументы.

Набор прагм для каждого компилятора языка Си различен. Для получения подробной информации о прагмах смотрите системную документацию по используемому вами компилятору.

Псевдопеременные

Псевдопеременные представляют собой зарезервированные именованные константы, которые можно использовать в любом исходном файле. Каждый из них начинается и оканчивается двумя символами подчеркивания (__).

Номер текущей обрабатываемой строки исходного файла-десятичная константа. Первая строка исходного файла имеет номер 1.

Имя компилируемого исходного файла - символьная строка. Значение данной псевдопеременной изменяется каждый раз, когда компилятор обрабатывает директиву #include или директиву #line, а также по завершении включаемого файла.

Следующие две псевдопеременные поддерживаются только СП ТС.

Дата начала компиляции текущего исходного файла - символьная строка. Каждое вхождение __DATE__ в заданный файл дает одно и то же значение, независимо от того, как долго уже продолжается обработка. Дата имеет формат mmm dd УУУУ, где mmm - месяц (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec), dd - число текущего месяца (1…31; в 1-й позиции dd ставится пробел, если число меньше 10), уууу - год (например, 1990).

Время начала компиляции текущего исходного файла - символьная строка. Каждое вхождение __TIME__ в заданный файл дает одно и то же значение, независимо от того, как долго уже продолжается обработка. Время имеет формат hh:mm:ss, где hh - час (00…23), mm - минуты (00…59), ss - секунды (00…59).

Заключительная глава книги посвящена описанию препроцессора C++. Препроцессор C++ - это часть компилятора, которая подвергает вашу программу различным текстовым преобразованиям до реальной трансляции исходного кода в объектный. Программист может давать препроцессору команды, называемые директивами препроцессора (preprocessor directives), которые, не являясь формальной частью языка C++, способны расширить область действия его среды программирования.

Препроцессор C++ включает следующие директивы.

Как видите, все директивы препроцессора начинаются с символа "#" .Теперь рассмотрим каждую из них в отдельности.

На заметку. Препроцессор C++ - прямой потомок препроцессора С, и некоторые его средства оказались избыточными после введения в C++ новых элементов. Однако он попрежнему является важной частью С++-среды программирования.

Директива #define

Директива #define определяет имя макроса.

Директива #define используется для определения идентификатора и символьной последовательности, которая будет подставлена вместо идентификатора везде, где он встречается в исходном коде программы. Этот идентификатор называется макроименем, а процесс замены -макроподстановкой (реализацией макрорасширения). Общий формат использования этой директивы имеет следующий вид.

#define макроимя последовательность_символов

Обратите внимание на то, что здесь нет точки с запятой. Заданная последовательность_символов завершается только символом конца строки. Между элементамимакроимя (имя_макроса) ипоследовательность_символов может быть любое количество пробелов.

Итак, после включения этой директивы каждое вхождение текстового фрагмента, определенное как макроимя , заменяется заданным элементомпоследовательность_символов . Например, если вы хотите использовать словоUP в качестве значения1 и словоDOWN в качестве значения0 , объявите такие директивы#define .

Данные директивы вынудят компилятор подставлять 1 или0 каждый раз, когда в файле исходного кода встретится словоUP илиDOWN соответственно. Например, при выполнении инструкции:

cout << UP << " " << DOWN << " " << UP + UP;

На экран будет выведено следующее:

После определения имени макроса его можно использовать как часть определения других макроимен. Например, следующий код определяет имена ONE ,TWO иTHREE и соответствующие им значения.

#define TWO ONE+ONE

#define THREE ONE+TWO

Важно понимать, что макроподстановка - это просто замена идентификатора соответствующей строкой. Следовательно, если вам нужно определить стандартное сообщение, используйте код, подобный этому.

Препроцессор заменит строкой "Введите имя файла" каждое вхождение идентификатораGETFILE . Для компилятора эта cout-инструкция

cout << GETFILE;

в действительности выглядит так.

cout << "Введите имя файла";

Никакой текстовой замены не произойдет, если идентификатор находится в строке, заключенной в кавычки. Например, при выполнении следующего кода

#define GETFILE "Введите имя файла"

cout << "GETFILE - это макроимя\n";

на экране будет отображена эта информация

GETFILE - это макроимя,

Введите имя файла - это макроимя

Если текстовая последовательность не помещается на строке, ее можно продолжить на следующей, поставив обратную косую черту в конце строки, как показано в этом примере.

#define LONG_STRING "Это очень длинная последовательность,\

которая используется в качестве примера."

Среди С++-программистов принято использовать для макроимен прописные буквы. Это соглашение позволяет с первого взгляда понять, что здесь используется макроподстановка. Кроме того, лучше всего поместить все директивы#define в начало файла или включить в отдельный файл, чтобы не искать их потом по всей программе.

Макроподстановки часто используются для определения "магических чисел" программы. Например, у вас есть программа, которая определяет некоторый массив, и ряд функций, которые получают доступ к нему. Вместо"жесткого" кодирования размера массива с помощью константы лучше определить имя, которое бы представляло размер, а затем использовать это имя везде, где должен стоять размер массива. Тогда, если этот размер придется изменить, вам достаточно будет внести только одно изменение, а затем перекомпилировать программу. Рассмотрим пример.

#define MAX_SIZE 100

float balance;

double index;

int num_emp;

Важно! Важно помнить, что в C++ предусмотрен еще один способ определения констант, который заключается в использовании спецификатора const. Однако многие программисты "пришли" в C++ из С-среды, где для этих целей обычно использовалась директива #define. Поэтому вам еще часто придется с ней сталкиваться в С++-коде.

Макроопределения, действующие как функции

Директива #define имеет еще одно назначение: макроимя может использоваться с аргументами. При каждом вхождении макроимени связанные с ним аргументы заменяются реальными аргументами, указанными в коде программы. Такие макроопределения действуют подобно функциям. Рассмотрим пример.

/* Использование "функциональных" макроопределений. */

#include

using namespace std;

#define MIN(a, b) (((a)<(b)) ? a: b)

cout << "Минимум равен: " << MIN(x, у);

При компиляции этой программы выражение, определенное идентификатором MIN (а, b) , будет заменено, ноx иy будут рассматриваться как операнды. Это значит, что coutинструкция после компиляции будет выглядеть так.

cout << "Минимум равен: " << (((х)<(у)) ? х: у);

По сути, такое макроопределение представляет собой способ определить функцию, которая вместо вызова позволяет раскрыть свой код в строке.

Макроопределения, действующие как функции, - это макроопределения, которые принимают аргументы.

Кажущиеся избыточными круглые скобки, в которые заключено макроопределение MIN , необходимы, чтобы гарантировать правильное восприятие компилятором заменяемого выражения. На самом деле дополнительные круглые скобки должны применяться практически ко всем макроопределениям, действующим подобно функциям. Нужно всегда очень внимательно относиться к определению таких макросов; в противном случае возможно получение неожиданных результатов. Рассмотрим, например, эту короткую программу, которая использует макрос для определения четности значения.

// Эта программа дает неверный ответ.

#include

using namespace std;

#define EVEN(a) a%2==0 ? 1: 0

if(EVEN(9+1)) cout << "четное число";

else cout << "нечетное число ";

Эта программа не будет работать корректно, поскольку не обеспечена правильная подстановка значений. При компиляции выражение EVEN(9+1) будет заменено следующим образом.

Напомню, что оператор "%" имеет более высокий приоритет, чем оператор"+" . Это значит, что сначала выполнится операция деления по модулю (% ) для числа1 , а затем ее результат будет сложен с числом9 , что (конечно же) не равно0 . Чтобы исправить ошибку, достаточно заключить в круглые скобки аргументa в макроопределенииEVEN , как показано в следующей (исправленной) версии той же программы.

// Эта программа работает корректно.

#include

using namespace std;

#define EVEN(a) (a)%2==0 ? 1: 0

if(EVEN(9+1)) cout << "четное число";

else cout << "нечетное число";

Теперь сумма 9+1 вычисляется до выполнения операции деления по модулю. В общем случае лучше всегда заключать параметры макроопределения в круглые скобки, чтобы избежать непредвиденных результатов, подобных описанному выше.

Использование макроопределений вместо настоящих функций имеет одно существенное достоинство: поскольку код макроопределения расширяется в строке, и нет никаких затрат системных ресурсов на вызов функции, скорость работы вашей программы будет выше по сравнению с применением обычной функции. Но повышение скорости является платой за увеличение размера программы (из-за дублирования кода функции).

Важно! Несмотря на то что макроопределения все еще встречаются в C++-коде, макросы, действующие подобно функциям, можно заменить спецификатором inline, который справляется с той же ролью лучше и безопаснее. (Вспомните: спецификатор inline обеспечивает вместо вызова функции расширение ее тела в строке.) Кроме того, inline-функции не требуют дополнительных круглых скобок, без которых не могут обойтись макроопределения. Однако макросы, действующие подобно функциям, все еще остаются частью С++-программ, поскольку многие С/С++-программисты продолжают использовать их по привычке.

Директива #еrror

Директива #error отображает сообщение об ошибке.

Директива #error дает указание компилятору остановить компиляцию. Она используется в основном для отладки. Общий формат ее записи таков.

#error сообщение

Обратите внимание на то, что элемент сообщение не заключен в двойные кавычки. При встрече с директивой#error отображается заданноесообщение и другая информация (она зависит от конкретной реализации рабочей среды), после чего компиляция прекращается. Чтобы узнать, какую информацию отображает в этом случае компилятор, достаточно провести эксперимент.

Директива #include

Директива #include включает заголовок или другой исходный файл.

Директива препроцессора #include обязывает компилятор включить либо стандартный заголовок, либо другой исходный файл, имя которого указано в директиве#include . Имя стандартных заголовков заключается в угловые скобки, как показано в примерах, приведенных в этой книге. Например, эта директива

#include

Включает стандартный заголовок для векторов.

При включении другого исходного файла его имя может быть указано в двойных кавычках или угловых скобках. Например, следующие две директивы обязывают C++ прочитать и скомпилировать файл с именем sample.h :

#include

#include "sample.h"

Если имя файла заключено в угловые скобки, то поиск файла будет осуществляться в одном или нескольких специальных каталогах, определенных конкретной реализацией.

Если же имя файла заключено в кавычки, поиск файла выполняется, как правило, в

текущем каталоге (что также определено конкретной реализацией). Во многих случаях это означает поиск текущего рабочего каталога. Если заданный файл не найден, поиск повторяется с использованием первого способа (как если бы имя файла было заключено в угловые скобки). Чтобы ознакомиться с подробностями, связанными с различной обработкой директивы #include в случае использования угловых скобок и двойных кавычек, обратитесь к руководству пользователя, прилагаемому к вашему компилятору. Инструкции#include могут быть вложенными в другие включаемые файлы.

Директивы условной компиляции

Существуют директивы, которые позволяют избирательно компилировать части исходного кода. Этот процесс, именуемый условной компиляцией , широко используется коммерческими фирмами по разработке программного обеспечения, которые создают и поддерживают множество различных версий одной программы.

Директивы #if, #else, #elif и #endif

Директивы #if, #ifdef, #ifndef, #else, #elif и #endif - это директивы условной компиляции.

Главная идея состоит в том, что если выражение, стоящее после директивы #if оказывается истинным, то будет скомпилирован код, расположенный между нею и директивой#endif в противном случае данный код будет опущен. Директива#endif используется для обозначения конца блока#if .

Общая форма записи директивы #if выглядит так.

#if константное_выражение

последовательность инструкций

Если константное_выражение является истинным, код, расположенный непосредственно за этой директивой, будет скомпилирован. Рассмотрим пример.

// Простой пример использования директивы #if.

#include

using namespace std;

cout << "Требуется дополнительная память\n";

При выполнении эта программа отобразит сообщение Требуется дополнительная память на экране, поскольку, как определено в программе, значение константыМАХ больше 10. Этот пример иллюстрирует важный момент: Выражение, которое стоит после директивы#if , вычисляется во время компиляции. Следовательно, оно должно содержать только идентификаторы, которые были предварительно определены, или константы. Использование же переменных здесь исключено.

Поведение директивы #else во многом подобно поведению инструкцииelse , которая является частью языка C++: она определяет альтернативу на случай невыполнения директивы#if . Чтобы показать, как работает директива#else , воспользуемся предыдущим примером, немного его расширив.

// Пример использования директив #if/#else.

#include

using namespace std;

cout << "Требуется дополнительная память.\n");

cout << "Достаточно имеющейся памяти.\n";

В этой программе для имени МАХ определено значение, которое меньше 10, поэтому#if - ветвь кода не скомпилируется, но зато скомпилируется альтернативная #else -ветвь. В результате отобразится сообщениеДостаточно имеющейся памяти. .

Обратите внимание на то, что директива #else используется для индикации одновременно как конца #if -блока, так и начала #еlse -блока. В этом есть логическая необходимость, поскольку только одна директива#endif может быть связана с директивой

#if.

Директива #elif эквивалентна связке инструкций else-if и используется для формирования многозвенной схемы if-else-if , представляющей несколько вариантов компиляции. После директивы#elif должно стоять константное выражение. Если это выражение истинно, следующий блок кода скомпилируется, и никакие другие#elif - выражения не будут тестироваться или компилироваться. В противном случае будет проверено следующее по очереди #elif -выражение. Вот как выглядит общий формат использования директивы#elif .

#if выражение

последовательность инструкций

#elif выражение 1

последовательность инструкций

#elif выражение 2

последовательность инструкций

#еlif выражение 3

последовательность инструкций

#elif выражение N

последовательность инструкций

Например, в этом фрагменте кода используется идентификатор COMPILED_BY , который позволяет определить, кем компилируется программа.

#define COMPILED_BY JOHN

#if COMPILED_BY == JOHN

char who = "John";

#elif COMPILED_BY == BOB

char who = "Bob";

char who = "Tom";

Директивы #if и#elif могут быть вложенными. В этом случае директива#endif ,#else или#elif связывается с ближайшей директивой#if или#elif . Например, следующий фрагмент кода совершенно допустим.

#if COMPILED_BY == BOB

#if DEBUG == FULL

#elif DEBUG == PARTIAL

cout << "Боб должен скомпилировать код" << "для отладки вывода данных.\n";

Директивы #ifdef и #ifndef

Директивы #ifdef и#ifndef предлагают еще два варианта условной компиляции, которые

можно выразить как "если определено"и "если не определено"соответственно.

Общий формат использования директивы #ifdef таков.

#ifdef макроимя

последовательность инструкций

Если макроимя предварительно определено с помощью какой-нибудь директивы#define , то

Общий формат использования директивы #ifndef таков.

#ifndef макроимя

последовательность инструкций

Если макроимя не определено с помощью какой-нибудь директивы#define , топоследовательность инструкций , расположенная между директивами#ifdef и#endif , будет скомпилирована.

Как директива #ifdef , так и директива#ifndef может иметь директиву#else или#elif . Рассмотрим пример.

#include

using namespace std;

cout << "Программист Том.\n";

cout << "Программист неизвестен.\n";

cout << "Имя RALPH не определено.\n";

При выполнении эта программа отображает следующее.

Программист Том.

Имя RALPH не определено.

Но если бы идентификатор ТОМ был не определен, то результат выполнения этой программы выглядел бы так.

Программист неизвестен.

Имя RALPH не определено.

И еще. Директивы #ifdef и#ifndef можно вкладывать точно так же, как и директивы#if .

Директива #undef

Директива #undef используется для удаления предыдущего определения некоторого макроимени. Ее общий формат таков.

#undef макроимя

Рассмотрим пример.

#define TIMEOUT 100

Здесь имена TIMEOUT иWAIT определены до тех пор, пока не выполнится директива

#undef.

Основное назначение директивы #undef - разрешить локализацию макроимен для тех частей кода, в которых они нужны.

Использование оператора defined

Помимо директивы #ifdef существует еще один способ выяснить, определено ли в программе некоторое макроимя. Для этого можно использовать директиву#if в сочетании с оператором времени компиляцииdefined . Например, чтобы узнать, определено ли макроимяMYFILE , можно использовать одну из следующих команд препроцессорной обработки.

#if defined MYFILE

При необходимости, чтобы реверсировать условие проверки, можно предварить оператор defined символом"!" . Например, следующий фрагмент кода скомпилируется только в том случае, если макроимяDEBUG не определено.

#if !defined DEBUG

cout << "Окончательная версия!\n";

О роли препроцессора

Препроцессор C++ - прямой потомок препроцессора языка С, причем без каких-либо усовершенствований. Однако его роль в C++ намного меньше роли, которую играет препроцессор в С. Дело в том, что многие задачи, выполняемые препроцессором в С, реализованы в C++ в виде элементов языка. Страуструп тем самым выразил свое намерение сделать функции препроцессора ненужными, чтобы в конце концов от него можно было бы совсем освободить язык.

На данном этапе препроцессор уже частично избыточен. Например, два наиболее употребительных свойства директивы #define были заменены инструкциями C++. В частности, ее способность создавать константное значение и определять макроопределение, действующее подобно функциям, сейчас совершенно избыточна. В C++ есть более эффективные средства для выполнения этих задач. Для создания константы достаточно определить const -переменную. А с созданием встраиваемой (подставляемой) функции вполне справляется спецификаторinline . Оба эти средства лучше работают, чем соответствующие механизмы директивы#define .

Приведем еще один пример замены элементов препроцессора элементами языка. Он связан с использованием однострочного комментария. Одна из причин его создания - разрешить "превращение" кода в комментарий. Как вы знаете, комментарий, использующий /*...*/ -стиль, не может быть вложенным. Это означает, что фрагменты кода, содержащие /*...*/ -комментарии, одним махом "превратить в комментарий" нельзя. Но это можно сделать с // -комментариями, окружив их /*...*/ -символами комментария. Возможность

"превращения" кода в комментарий делает использование таких директив условной компиляции, как #ifdef , частично избыточным.

Директива #line

Директива #line изменяет содержимое псевдопеременных _ _LINE_ _ и _ _FILE_ _.

Директива #line используется для изменения содержимого псевдопеременных_ _LINE_ _ и_ _FILE_ _ , которые являются зарезервированными идентификаторами (макроименами). Псевдопеременная_ _LINE_ _ содержит номер скомпилированной строки, а псевдопеременная_ _FILE_ _ - имя компилируемого файла. Базовая форма записи этой команды имеет следующий вид.

#line номер "имя_файла"

Здесь номер - это любое положительное целое число, аимя_файла - любой допустимый идентификатор файла. Значение элементаномер становится номером текущей исходной строки, а значение элементаимя_файла - именем исходного файла.Имя_файла

Элемент необязательный. Директива #line используется, главным образом, в целях отладки и в специальных приложениях.

Например, следующая программа обязывает начинать счет строк с числа 200. Инструкция cout отображает номер 202, поскольку это - третья строка в программе после директивной инструкции #line 200 .

#include

using namespace std;

#line 200 // Устанавливаем счетчик строк равным 200.

int main() // Эта строка сейчас имеет номер 200.

{// Номер этой строки равен 201.

cout << _ _LINE_ _;// Здесь выводится номер 202.

Директива #pragma

Директива #pragma зависит от конкретной реализации компилятора.

Работа директивы #pragma зависит от конкретной реализации компилятора. Она позволяет выдавать компилятору различные инструкции, предусмотренные создателем компилятора. Общий формат его использования таков.

Здесь элемент имя представляет имя желаемой #pragma -инструкции. Если указанное

имя не распознается компилятором, директива #pragma попросту игнорируется без сообщения об ошибке.

Важно! Для получения подробной информации о возможных вариантах использования директивы #pragma стоит обратиться к системной документации по используемому вами компилятору. Вы можете найти для себя очень полезную информацию. Обычно #pragmaинструкции позволяют определить, какие предупреждающие сообщения выдает компилятор, как генерируется код и какие библиотеки компонуются с вашими программами.

Операторы препроцессора "#" и "##"

В C++ предусмотрена поддержка двух операторов препроцессора: "#" и"##" . Эти операторы используются совместно с директивой#define . Оператор"#" преобразует следующий за ним аргумент в строку, заключенную в кавычки. Рассмотрим, например, следующую программу.

#include

using namespace std;

#define mkstr(s) # s

cout << mkstr(Я в восторге от C++);

Препроцессор C++ преобразует строку

cout << mkstr(Я в восторге от C++);

cout << "Я в восторге от C++";

Оператор используется для конкатенации двух лексем. Рассмотрим пример.

#include

using namespace std;

#define concat(a, b) a ## b

cout << concat(x, y);

Препроцессор преобразует строку

cout << concat (x, y);

cout << xy;

Если эти операторы вам кажутся странными, помните, что они не являются операторами "повседневного спроса" и редко используются в программах. Их основное назначение - позволить препроцессору обрабатывать некоторые специальные ситуации.

Зарезервированные макроимена

В языке C++ определено шесть встроенных макроимен.

Рассмотрим каждое из них в отдельности.

Макросы _ _LINE_ _ и_ _FILE_ _ описаны при рассмотрении директивы#line выше в этой главе. Они содержат номер текущей строки и имя файла компилируемой программы.

Макрос _ _DATE_ _ представляет собой строку в форматемесяц/день/год , которая означает дату трансляции исходного файла в объектный код.

Время трансляции исходного файла в объектный код содержится в виде строки в макросе _ _TIME_ _ . Формат этой строки следующий:часы.минуты.секунды .

Точное назначение макроса _ _STDC_ _ зависит от конкретной реализации компилятора. Как правило, если макрос_ _STDC_ _ определен, то компилятор примет только стандартный С/С++-код, который не содержит никаких нестандартных расширений.

Компилятор, соответствующий ANSI/ISO-стандарту C++, определяет макрос_ _cplusplus как значение, содержащее по крайней мере шесть цифр. "Нестандартные" компиляторы должны использовать значение, содержащее пять (или даже меньше) цифр.

Мысли "под занавес"

Мы преодолели немалый путь: длиной в целую книгу. Если вы внимательно изучили все приведенные здесь примеры, то можете смело назвать себя программистом на C++. Подобно многим другим наукам, программирование лучше всего осваивать на практике, поэтому теперь вам нужно писать побольше программ. Полезно также разобраться в С++- программах, написанных другими (причем разными) профессиональными программистами. При этом важно обращать внимание на то, как программа оформлена и реализована. Постарайтесь найти в них как достоинства, так и недостатки. Это расширит диапазон ваших представлений о программировании. Подумайте также над тем, как можно улучшить существующий код, применив контейнеры и алгоритмы библиотеки STL. Эти средства, как правило, позволяют улучшить читабельность и поддержку больших программ. Наконец, просто больше экспериментируйте! Дайте волю своей фантазии и вскоре вы почувствуете себя настоящим С++-программистом!

Для продолжения теоретического освоения C++ предлагаю обратиться к моей книге Полный справочник по C++ , М. : Издательский дом "Вильямс". Она содержит подробное описание элементов языка C++ и библиотек.

#include

Директива #include вставляет код из указанного файла в текущий файл, то есть, просто подключив другой файл, мы можем пользоваться его функциями, классами, переменными. Заголовочные файлы обычно находятся либо в текущей директории, либо в стандартном системном каталоге.

Подключение заголовочных файлов выполняется во время компиляции, либо как файл, который является частью вашего проекта. Эта функция зависит от конкретной реализации вашего компилятора, поэтому для получения более подробной информации, покопайтесь в настройках своего компилятора.

Если подключаемый файл не найден, процесс компиляции завершается с ошибкой.

Директива #define

Директива #define принимает две формы:

  • определение констант;
  • определение макросов.
Определение констант
#define nameToken value

При использовании имени константы — nameToken , оно будет заменено значением value , то есть, грубо говоря — это та же самая переменная, значение которой изменить нельзя. Смотрим пример использования константы:

#include #define TEXT "Марс" // определение константы int main() { std::cout << TEXT; return 0; }

Как видите, для доступа к значению константы, просто используем её имя.

Определение параметризованных макросов

#define nameMacros(arg1, arg2, ...) expression

К примеру определим макрос, который будет возвращать максимальное из двух значений.

#define MAX(num1, num2) ((num1) > (num2) ? (num1) : (num2))

Внимание, для определения многострочного макроса, в каждой строке, в конце, должен ставиться символ, что сообщает препроцессору, что макрос еще не завершен.

Директива #undef

Директива #undef переопределяет константу или препроцессорный макрос, ранее определенный с помощью директивы #define .

#undef nameToken

Давайте посмотрим пример использования директивы #undef:

#define E 2.71828 // раннее определенный макрос int sumE = E + E; // обращение к макросу #undef E // теперь E - не макрос

Как правило, директива #undef используются для снятия, ранее определенной константы или макроса, в небольшой области программы. Это делается для того, чтобы для всей программы, макроc или константа оставались, а для некоторой области, эти же макрос или константа могут быть переопределены. Небезопасно было бы во всей программе переопределять константу, но в короткий области, это сравнительно безопасно. Директива #undef является единственным способом создания этой области, так как область действия макросов или констант действует от директивы #define до #undef .

Директива #if

#if value // код, который выполнится, в случае, если value - истина #elsif value1 // этот код выполнится, в случае, если value1 - истина #else // код, который выполнится в противном случае #endif

Директива #if проверяет, является ли значение value истиной и, если это так, то выполняется код, который стоит до закрывающей директивы #endif . В противном случае, код внутри #if не будет компилироваться, он будет удален компилятором, но это не влияет на исходный код в исходнике.

Обратите внимание, что в #if могут быть вложенные директивы #elsif и #else . Ниже показан пример кода для комментирования блоков кода, используя следующую конструкцию:

#if 0 // код, который необходимо закомментировать #endif

Если у вас в программе есть блоки кода, которые содержат многострочные комментарии и вам требуется обернуть полностью этот блок кода в комментарий — ничего не получится, если вы воспользуетесь /*многострочный комментарий*/ . Другое дело — конструкция директив #if #endif .

Директива #ifdef

#ifdef nameToken // код, который выполнится, если nameToken определен #else // код, который выполнится, если nameToken не определен #endif

Директива #ifdef проверяет, был ли ранее определен макрос или символическая константа как #define . Если — да, компилятор включает в программу код, который находится между директивами #ifdef и #else , если nameToken ранее определен не был, то выполняется код между #else и #endif , или, если нет директивы #else , компилятор сразу переходит к #endif . Например, макрос __cpp определен в C++, но не в Си. Вы можете использовать этот факт для смешивания C и C++ кода, используя директиву #ifdef:

#ifdef __cpp // C++ код #else // Си код #endif

Директива #ifndef

#ifndef nameToken // код, который выполнится, если nameToken не определен #else // код, который выполнится, если nameToken определен #endif

Директива #ifndef проверяет, был ли ранее определен макрос или символическая константа как #define . Если — да, компилятор включает в программу код, который находится между директивами #else и #endif , если nameToken ранее определен не был, то выполняется код между #ifndef и #else , или, если нет директивы #else , компилятор сразу переходит к #endif . Директива #ifndef может быть использована для подключения заголовочных файлов. если они не подключены, для этого использовать символическую константу, как индикатор подключенного к проекту функционала.

Например, в заголовочном файле есть интерфейс класса, который необходимо подключить к проекту, если ранее этот класс не был подключен.

#ifndef PRODUCT_H #define PRODUCT_H class Product { // код класса... }; #endif PRODUCT_H

В этом случае используется пустая символьная константа PRODUCT_H , которая может быть определена в программе только вместе с классом Product . Поэтому, если мы обнаружим, что константа PRODUCT_H уже определена, значит класс тоже и тогда мы исключим повторное определение класса, которое может привести к ошибке переопределения.

Директива #error

#error "Этот код не должен компилироваться"

Директива #error позволяет отображать в списке ошибок компиляции сообщение, в случае возникновения соответствующей ошибки. Эту директиву наиболее полезно использовать в сочетании с директивами #if , #elsif , #else для проверки компиляции, если некоторое условие не верно. Например:

#ifndef __unix__ // __unix__ обычно поддерживается в юникс-системах #error "Поддерживается только в Unix" #endif

Препроцессорный макрос __FILE__

Препроцессорный макрос __FILE__ расширяется до полного пути к текущему файлу (исходнику). __FILE__ полезен при создании лог-файла, генерации сообщений об ошибках, предназначенных для программистов, при отладки кода.

Int error (const char* adrFile, const std::string& erMessage) { cerr << "[" << adrFile << "]" << arMessage << endl; } #define LOG(erMessage) error(__FILE__, arMessage) // макрос LOG может быть использован для получения сообщений об ошибках, которые выводятся на стандартный поток ошибок

Макрос __FILE__ часто используется совместно с макросом __LINE__ , который предоставляет номер текущей строки.

Препроцессорный макрос __LINE__

Макрос __LINE__ разворачивается в текущий номер строки в исходном файле, как целое значение. __LINE__ полезен при создании лог-файла или генерации сообщений об ошибках с указанием номера строки, предназначенных для программистов, при отладки кода.

Int error (int nLine, const std::string& erMessage) { cerr << "[" << nLine << "]" << erMessage << endl; } #define LOG(erMessage) error(__LINE__, erMessage) // макрос LOG может быть использован для получения сообщений об ошибках, с указанием номеров строк, которые выводятся на стандартный поток ошибок

Макрос __LINE__ часто используется совместно с макросом __FILE__ , который показывает адрес текущего исходного файла.

Препроцессорный макрос __DATE__

Макрос __DATE__ раскрывается в текущую дату (время компиляции) в виде [ммм дд гггг] (например, «Dec 7 2012″), как строка. __DATE__ может быть использован для предоставления информации о времени компиляции.

Cout << __DATE__ << endl;

Вы можете также использовать макрос __TIME__ , чтобы получить текущее время компиляции.

Препроцессорный макрос __TIME__

Макрос __TIME__ раскрывается в текущее время (время компиляции) в формате чч: мм:cc в 24-часовом формате (например, «22:29:12″). Макрос __TIME__ может быть использован для предоставления информации о времени в конкретный момент компиляции.

Cout << __TIME__ << endl;

Препроцессорный макрос __TIMESTAMP__

Макрос __TIMESTAMP__ раскрывается в текущее время (время компиляции) в формате Ddd Mmm Date hh::mm::ss yyyy, время в 24-часовом формате:

  • Ddd это сокращенно день недели,
  • ммм это сокращенно месяц,
  • Date — текущий день месяца (1-31),
  • гггг — это четыре цифры года.

Например, "Fri Dec 7 00:42:53 2012" . Макрос __TIMESTAMP__ может быть использован для получения информации о дате и времени компиляции.

Cout << __TIMESTAMP__ << endl;

Вы можете также использовать макрос __TIME__ , чтобы получить текущее время компиляции и макрос __DATE__ для получения даты.

Директива #pragma

#pragma compiler specific extension

Директива #pragma используется для доступа к специфическим расширениям компилятора. Совместное использование директивы #pragma c лексемой once просит компилятор включить файл заголовка только один раз, независимо от того, сколько раз она был импортирован:

#pragma once // заголовочный файл

В этом примере, директива #pragma once не позволяет включать файл в проект несколько раз, то есть предотвращает переопределение.

Директива #pragma также может быть использована для других целей, например #pragma обычно используется для отключения предупреждений. Например, в MVS:

#pragma warning (disable: 4018)

Директива #pragma в этом примере используется для отключения предупреждений 4018. Для получения дополнительной использования директивы #pragma , обратитесь к документации вашего компилятора.

Макро оператор #

#

Оператор # текстовую лексему в строку, заключенную в кавычку. Смотрим пример:

#include using namespace std; #define message(s) cout << "Сообщение: " #s << endl; int main() { message("GunGame"); return 0; }

Выполняется конкатенация строк и макрос message разворачивается в cout << "Сообщение: GunGamen"; . Обратите внимание на то, что операция # должна использоваться совместно с аргументами, так как # ссылается на аргумент.

Макро оператор ##

Оператор ## принимает две отдельных лексемы и склеивает их вместе, чтобы сформировать один макрос. В результате может получиться имя переменной, имя класса или любой другой идентификатор. Например:

#define type ch##ar type a; // переменная a тип данных char, так как ch и ar склеились в char

Рассмотрим еще один пример использования оператора ## , в котором объединим две лексемы:

#define TOKENCONCAT(x,y) x##y

Когда в программе выполняется вызов этого макроса, две лексемы объединяются в одну. Операция ## обязательно должна иметь два операнда.

P.S.: Любая серьёзная программа должна иметь свою базу данных, обычно для управления БД используются следующие СУБД: MySQL, MsSQL, PostgreeSQL, Oracle и др. В установке sql server, форум на cyberforum.ru будет для вас не заменимым помощником. Задавайте свои вопросы на этом форуме, вам обязательно помогут в решении вашей проблемы.

Почти все программы на языке С++ используют специальные команды для компилятора, которые называются директивами. В общем случае директива – это указание компилятору языка С++ выполнить то или иное действие в момент компиляции программы. Существует строго определенный набор возможных директив, который включает в себя следующие определения:

#define, #elif, #else, #endif, #if, #ifdef, #ifndef, #include, #undef.

Директива #define используется для задания констант, ключевых слов, операторов и выражений, используемых в программе. Общий синтаксис данной директивы имеет следующий вид:

Следует заметить, что символ ‘;’ после директив не ставится. Приведем примеры вариантов использования директивы #define.

Листинг 1.2. Примеры использования директивы #define.

#include
#define TWO 2
#define FOUR TWO*TWO
#define PX printf(“X равно %d.\n”, x)
#define FMT «X равно %d.\n»
#define SQUARE(X) X*X
int main()
{
int x = TWO;
PX;
x = FOUR;
printf(FMT, x);
x = SQUARE(3);
PX;

Return 0;
}

После выполнения этой программы на экране монитора появится три строки:

X равно 2.
X равно 4.
X равно 9.

Директива #undef отменяет определение, введенное ранее директивой #define. Предположим, что на каком-либо участке программы нужно отменить определение константы FOUR. Это достигается следующей командой:

Интересной особенностью данной директивы является возможность переопределения значения ранее введенной константы. Действительно, повторное использование директивы #define для ранее введенной константы FOUR невозможно, т.к. это приведет к сообщению об ошибке в момент компиляции программы. Но если отменить определение константы FOUR с помощью директивы #undef, то появляется возможность повторного использования директивы #define для константы FOUR.

Для того чтобы иметь возможность выполнять условную компиляцию, используется группа директив #if, #ifdef, #ifndef, #elif, #else и #endif. Приведенная ниже программа выполняет подключение библиотек в зависимости от установленных констант.

#if defined(GRAPH)
#elif defined(TEXT)
#else
#endif

Данная программа работает следующим образом. Если ранее была задана константа с именем GRAPH через директиву #define, то будет подключена графическая библиотека с помощью директивы #include. Если идентификатор GRAPH не определен, но имеется определение TEXT, то будет использоваться библиотека текстового ввода/вывода. Иначе, при отсутствии каких-либо определений, подключается библиотека ввода/вывода. Вместо словосочетания #if defined часто используют сокращенные обозначения #ifdef и #ifndef и выше приведенную программу можно переписать в виде:

#ifdef GRAPH
#include //подключение графической библиотеки
#ifdef TEXT
#include //подключение текстовой библиотеки
#else
#include //подключение библиотеки ввода-вывода
#endif

Отличие директивы #if от директив #ifdef и #ifndef заключается в возможности проверки более разнообразных условий, а не только существует или нет какие-либо константы. Например, с помощью директивы #if можно проводить такую проверку:

#if SIZE == 1
#include // подключение математической библиотеки
#elif SIZE > 1
#include // подключение библиотеки обработки массивов
#endif

В приведенном примере подключается либо математическая библиотека, либо библиотека обработки массивов, в зависимости от значения константы SIZE.

Данные директивы иногда используются для выделения нужных блоков программы, которые требуется использовать в той или иной программной реализации. Следующий пример демонстрирует работу такого программного кода.

Листинг 1.3. Пример компиляции отдельных блоков программы.

#include
#define SQUARE
int main()
{
int s = 0;
int length = 10;
int width = 5;

#ifdef SQUARE
s=length*width;
#else
s=2*(length+width);
#endif

Return 0;
}

В данном примере происходит вычисление либо площади прямоугольника, либо его периметра, в зависимости от того определено или нет значение SQUARE. По умолчанию программа вычисляет площадь прямоугольника, но если убрать строку #define SQUARE, то программа станет вычислять его периметр.

Используемая в приведенных примерах директива #include позволяет добавлять в программу ранее написанные программы и сохраненные в виде файлов. Например, строка

#include < stdio.h >

указывает препроцессору добавить содержимое файла stdio.h вместо приведенной строки. Это дает большую гибкость, легкость программирования и наглядность создаваемого текста программы. Есть две разновидности директивы #include:

#include < stdio.h > - имя файла в угловых скобках

#include «mylib.h» - имя файла в кавычках

Угловые скобки сообщают препроцессору о том, что необходимо искать файл (в данном случае stdio.h) в одном или нескольких стандартных системных каталогах. Кавычки свидетельствуют о том, что препроцессору необходимо сначала выполнить поиск файла в текущем каталоге, т.е. в том, где находится файл создаваемой программы, а уже затем – искать в стандартных каталогах.