11.1  Директивы #define и #undef

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

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

#define  идентификатор (список параметров) текст

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

#define WIDTH 80

#define LENGTH (WIDTH+10)

Эти директивы изменят в тексте программы каждое слово WIDTH на число 80, а каждое слово LENGTH на выражение (80+10) вместе с его скобками.

Скобки, содержащиеся в макроопределении, позволяют избежать недоразумений, связанных с порядком вычисления операций. Например, при отсутствии скобок выражение t=LENGTH*7 будет преобразовано в выражение t=80+10*7, а не в выражение t=(80+10)*7, как это получается при наличии скобок, следовательно, получим разные результаты.

Приведем примеры использования директивы #define первой синтаксической формы:

#define HI "Добрый день!"

#define empty

#define NIL " "

puts(HI);            /* расширяется в puts("Добрый день!"); */

puts(NIL);          /* расширяется в puts(" "); */

puts("empty"); /* расширения empty не происходит ! */

/* расширение empty не будет выполнено и в комментариях! */   

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

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

Эта директива заменит фрагмент

t=MAX(i,s[i]);

на фрагмент

t=((i)>(s[i])?(i):(s[i]);

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

Например, при наличии скобок фрагмент

t=MAX(i&j,s[i]||j);

будет заменен на фрагмент

t=((i&j)>(s[i]||j)?(i&j):(s[i]||j);

а при отсутствии скобок – на фрагмент

t=(i&j>s[i]||j)?i&j:s[i]||j;

в котором условное выражение вычисляется совершенно в другом порядке.

Приведем еще пример:

#define ERRMSG(x, str) showerr("Error",x,str)

#define  SUM(x,y) ((x) + (y))

ERRMSG(2, "Press Enter, then Esc");

// расширится в: showerr("Error",2,"Press Enter, then Esc");

return SUM(f(i,j), g(k,l));

// расширится в: return ((f(i,j)) + (g(k,l)));

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

#define ABS(X)  ((X) < 0 ? – (X) : (X))

#define MIN(X,Y)  ((X) < (Y) ? (X) : (Y)

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

#define LIMIT 100  // первое определение

#undef LIMIT         // отмена первого определения

#define LIMIT 50   // второе определение

#undef LIMIT        // отмена второго определения

#define LIMIT 25   // третье определение