Использование директивы #define

С помощью директивы #define можно вводить собственные обозначения базовых или производных типов.

Пример:

Директива

#define REAL long double

вводит имя REAL для типа long double. Далее в тексте программы можно определять объекты типа long double, используя данное имя:

REAL x, array [6];

Такой прием удобно использовать для присвоения более коротких имен сложным типам данных.

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

Пример:

После записи директивы

#define PN printf (\n Номер элемента=%d, N);

Последовательность операторов

int N=4;

PN;

выведет на экран текст

Номер элемента=4

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

Доступ к элементам многомерных массивов в С++ имеет две особенности, которые создают неудобства при работе с ними:

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

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

Пример:

#define N 4 // число строк матрицы

#define M 5 // число столбцов матрицы

#define A(i,j) x[M*(i-l) + (j-1)]

#include

void main ( )

{

/* Определение одномерного массива */

double x[N*M];

int i, j, k;

for (k=0; kN*M; k++) x[k]=k;

/* Перебор строк */

for (i=1; i

printf (\n Строка %d:, i);

for (j=1; j

}

}

Результат выполнения программы:

Строка 1: 0.0 1.0 2.0 3.0 4.0

Строка 2: 5.0 6.0 7.0 8.0 9.0

Строка 3: 10.0 11.0 12.0 13.0 14.0

Строка 4: 15.0 16.0 17.0 18.0 19.0

В программе создается виртуальный многомерный массив, размерностью N?М (N – число строк, M — число столбцов). Размерность массива задается на этапе препроцессорной обработки с помощью директивы #define. Для создания виртуального многомерного массива в программе используется макроопределения и одномерный (реальный) массив x[] размерностью N*M. Таким образом, элементы виртуального многомерного массив будут размешаться в одномерном массиве построчно. Значения элементам массива присваиваются в цикле с параметром k.

На рисунке 1.11 приведена схема одномерного массива х[ ] для моделирования виртуального двумерного массива с помощью макоопределений.

Рисунок 1.11 – Имитация многомерного массива с помощью одномерного массива и макроопределения.

Для доступа к элементам массива используются макровызовы A(i, j). Индекс i соответствует номеру строки многомерного массива и изменяется от 1 до N, а индекс j – номеру столбца и изменяется во внутреннем цикле от 1 до М. A(i,j) является достаточно естественным обозначениями элементов матрицы, причем нумерация столбцов и строк начинается с 1.

За счет применения макросов выполняются замены параметризованных обозначений A(i, j) на x[5*(i-l)+(j-l)]. Далее действия выполняются над элементами одномерного массива х[ ], но т.к. данные преобразования выполняются на этапе препроцессорной обработки можно считать, что осуществляется работа с традиционными для многомерных массивов обозначениями.

Использованный в программе оператор

printf (“% 6.1f”, A (i, j));

после макроподстановок будет иметь вид:

printf (“% 6.1f”, x[5*(i-l)+(j-l)]);

Например:

A (1,1) соответствует x[0] — вычислено как x[5(1-1)+(1-1)]

A (1,2) соответствует x[1] – вычислено x[5(1-1)+(2-1)]

A (2,1) соответствует x[5] – вычислено как x[5(2-1)+(1-1)]

A (3,4) соответствует x[13] – вычислено как x[5(3-1)+(1-1)]

Макросы унаследованы из языка С, при написании программ на C++ их следует избегать. Вместо макросов без параметров предпочтительнее использовать const или enum, а вместо параметризованных макросов — встроенные функции или шаблоны.

Директива #undef отменяет действие директивы #define.

Данная директива имеет следующий формат:

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

После выполнения директивы идентификатор, ранее определенный директивой #define, для препроцессора становится неопределенным, и его можно определять повторно.

Пример:

Если для переопределения константы M использовать последовательность директив

#define M 16

#undef M

#define М ‘С’

#undef M

#define M “С”

никаких предупреждающих сообщений выдано не будет (как это было в рассмотренном ранее примере без использования директивы #undef).

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

Пример:

А = 10; // Основной текст

#define A X

А = 5; // Включенный текст

#undef A

В = А; // Основной текст

При выполнении программы переменная В примет значение 10, несмотря на наличие оператора присваивания А = 5; во включенном тексте.

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

Данная директива имеет две формы записи:

#include

#include “имя_файла”

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

Имя файла может быть указано с расширением. Файлы с расширением .h, называются заголовочными файлами (header file). Они могут содержать:

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

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

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

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

#includestdio.h .

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

Перечень заголовочных файлов утвержден стандартом языка. Перечислим некоторые заголовочные файлы языка С++:

float.h — работа с вещественными данными

limits.h — предельные значения целочисленных данных

math.h — математические вычисления

stdio.h — средства ввода-вывода

string.h — работа со строками символов

time.h — определение дат и времени

В конкретных реализациях состав и наименования заголовочных файлов могут отличаться. Например, в компиляторах для MS-DOS активно используются файлы mem.h, alloc.h, conio.h, dos.h, graphics.h и др.

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

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

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

Внешние объекты должны записываться в заголовочном файле со спецификатором extern. Например:

extern int ii, jj, ll; // целые внешние переменные

extern float aa, bb; // вещественные внешние переменные

Термин «заголовочный файл» обусловлен тем, что включение таких файлов желательно помещать в начале текста программы (заведомо раньше обращений к объектам и функциям, определенным в данном файле). Хотя это можно сделать и непосредственно перед обращением к функции, описанной в заголовочном файле, этого делать не рекомендуется.

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

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

Директивы #if, #ifdef, #ifndef выполняют проверку условий.

Общая структура их применения имеет следующий формат:

#if | #ifdef | #ifndef условие

текст_1

[#else

текст_2]

#endif

Директива #endif указывает окончание действия директив #if, #ifdef и #ifndef.

Текст_1включается в компилируемый текст при истинности проверяемого условия. Директива #else определяет начало альтернативной ветви и не является обязательной. Если условие ложно, то при ее наличии ветви #else в компилируемый текст включается текст_2, при ее отсутствии весь текст от #if до #endif опускается.

Различие между директивами #if, #ifdef и #ifndef состоит в типе проверяемого условия. Директива #if задает проверку условия-выражения, директива #ifdef — проверку определенности идентификатора, #ifndef — проверка неопределенности идентификатора.

Директива #if имеет формат

#if выражение

Выражение может содержать целые константы и идентификаторы. Если идентификаторы определены на препроцессорном уровне, их значение определяется макроподстановками, в противном случае они имеет нулевые значения. Проверяемое условие истинно, если константное выражение отлично от нуля.

Пример:

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

#if 5+4

текст_1

#endif

текст_1всегда будет включен в компилируемую программу.

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

Пример:

#if 0

int i, j;

double x, y;

#endif

Директива #ifdef имеет формат

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

Проверяемое условие истинно, если идентификатор является препроцессорным, т.е. ранее определен директивой #define.

Директива #ifndef имеет формат

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

Проверяемое условие истинно, если идентификатор не является препроцессорным, т.е. ранее не определен директивой #defineили его определение было отменено директивой #undef.

Определение идентификатора, управляющего условной компиляцией, осуществляется с помощью третьей модификации директивы #define:

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

Строка замещения в данном случае отсутствует.

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

Пример:

После определения идентификатора DEBUG с помощью директивы

#define DEBUG

В программе можно использовать для вывода контрольного сообщения конструкцию

#ifdef DEBUG

printf ( “Отладочная печать”);

#endif

Оператор printf (“Отладочная печать”); выполняется, т.к. идентификатор DEBUG ранее определен. Вывод контрольного сообщения может использоваться программе неоднократно, но убрав либо поместив в скобки комментария директиву #define DEBUG, можно отключить их все сразу.

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

Пример:

#ifndef _FILE_NAME // проверка определенности _FILE_NAME

#include filename.h // включение текста файла filename.h

#define _FILE_NAME // определение _FILE_NAME

#endif

Здесь _FILE_NAME — зарезервированный для файла с именем filename препроцессорный идентификатор. Его нежелательно использовать в других текстах программы.

Директива #elif является составной директивой #else — #if. Она используется для организации множественных ветвлений.

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

#elif выражение

Требования к выражению такие же, как и для директивы #if.

Формат применения данной директивы:

#if выражение_0

текст_1

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

текст_2

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

текст_3

#else

текст_для__еlse

#endif

Количество директив #elif является произвольным.

Препроцессор сначала проверяет выражение_0 в директиве #if. Если оно не равно 0, в компилируемый текст включается текст_1f, если оно равно 0, вычисляется выражение_1. Если выражение_1 не равно 0 в текст включается текст_2, если оно равно 0, вычисляется выражение_2 и т.д. Если все выражения равны 0, то в компилируемый текст включается текст_для_else.

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

Пример:

#if VERSION == 1

#define INCFILE versl.h

#elif VERSION ==2

#define INCFILE vers2.h

#else

#define INCFILE vers3.h

#endif

#include INCFILE

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

Операция defined

При использовании директив #if и #elifдля упрощения записи сложного условия выбора можно использовать унарную препроцессорную операцию defined. Она имеет формат:

defined операнд

В качестве операнда может выст