Требуется директива препроцессора visual studio. Директивы препроцессора и указания компилятору

#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 будет для вас не заменимым помощником. Задавайте свои вопросы на этом форуме, вам обязательно помогут в решении вашей проблемы.

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

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

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

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

#include

#include «header2.h»

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

#ifndef CUCUMBLER_H

#define CUCUMBLER_H

/* содержимое файла cucumbler.h */

Директива #ifndef выполняет проверку не была ли определена константа CUCUMBLER_H ранее, и если ответ отрицательный, то выполняется определение данной константы, и прочего кода, который следует до директивы #endif. Как не сложно догадаться директива #define определяет константу CUCUMBLER_H. В данном случае подобный кусок кода помогает избежать многократного включения одного и того же кода, так как после первого включения проинициализируется константа CUCUMBLER_H и последующие проверки #ifndef CUCUMBLER_H будут возвращать FALSE.

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

#include

#include

#include

using namespace std;

cout << "Начало функции main()\n";

vector text_array;

while (cin >> text)

cout << "Прочитан текст: " << text << "\n";

text_array.push_back(text);

Если константа IN_DEBUG не задана, то препроцессор сгенерирует следующий исходник:

#include

#include

#include

using namespace std;

vector text_array;

while (cin >> text)

text_array.push_back(text);

Но если определить IN_DEBUG, то текст программы кардинальным образом поменяется

#include

#include

#include

using namespace std;

cout << "Начало функции main()\n";

vector text_array;

while (cin >> text)

cout << "Прочитан текст: " << text << "\n";

text_array.push_back(text);

Задать препроцессорную константу можно прямо из консоли. Например для компилятора g++ применяется следующий формат

Заключительная глава книги посвящена описанию препроцессора 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 - вставляет текст из указанного файла
#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;
}

препроцессор . Назначение препроцессора - обработка исходного текста программы до ее компиляции. Препроцессорная обработка включает несколько стадий, выполняемых последовательно. Конкретная реализация может объединять несколько стадий, но результат должен быть таким, как если бы они выполнялись в следующем порядке:
  1. Все системно-зависимые обозначения перекодируются в стандартные коды.
  2. Каждая пара из символов " \ " и "конец строки" вместе с пробелами между ними убираются, и тем самым следующая строка исходного текста присоединяется к строке, в которой находилась эта пара символов.
  3. В тексте распознаются директивы и лексемы препроцессора, а каждый комментарий заменяется одним символом пустого промежутка.
  4. Выполняются директивы препроцессора и производятся макроподстановки.
  5. Эскейп-последовательности в символьных константах и символьных строках заменяются на их эквиваленты.
  6. Смежные символьные строки конкатенируются, то есть соединяются в одну строку.
  7. Каждая препроцессорная лексема преобразуется в текст на языке Си.

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

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

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

Символические константы: #define

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

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

Замена идентификаторов

#define идентификатор строка

Заменяет каждое вхождение идентификатора ABC в тексте программы на 100 :

#undef идентификатор

Отменяет предыдущее определение для идентификатора ABC .

/* Простые примеры директивы препроцессора */ #define TWO 2 /* можно использовать комментарии*/ #define MSG "Текст 1.\ Продолжение текста 1" /* обратная косая черта продолжает определение на следующую строку */ #define FOUR TWO*TWO #define PX printf("X равен %d.\n", x) #define FMT "X равен %d.\n" int main() { int x = TWO; PX; x = FOUR; printf(FMT,x); printf("%s\n",MSG); printf("TWO: MSG\n"); return TWO; }

В результате выполнения нашего примера будем иметь.



Понравилась статья? Поделиться с друзьями: