Директива #define определяет идентификатор и последовательность символов, которой будет замещаться данный идентификатор при его обнаружении в тексте программы. Идентификатор также называется именем макроса, а процесс замещения называется подстановкой макроса. Стандартный вид директивы следующий:
#define имя_макроса последовательность_символов
Обратим внимание, что в данном операторе отсутствует точка с запятой. Между идентификатором и последовательностью символов может быть любое число пробелов. Макрос завершается только переходом на новую строку.
Например, если необходимо использовать TRUE для значения 1, a FALSE для 0 то можно объявить следующие два макроса:
#define TRUE 1
#define FALSE 0
В результате, если компилятор обнаружит в тексте программы TRUE или FALSE, то он заменит их на 1 и 0 соответственно. Например, следующая строка выводит на экран «0 1 2»:
printf ("%d %d %d", FALSE, TRUE, TRUE + 1);
В случае, если макрос определен, он может использоваться для определения других макросов. Например, следующий код сопоставляет с именами ONE, TWO и THREE их численные значения:
#define ONE 1
#define TWO ONE + ONE
#def ine THREE ONE + TWO
В результате макроподстановки идентификаторы замещаются указанными строками. Если необходимо определить стандартное сообщение об ошибке, то можно написать что-то вроде следующего:
#define E_MS "Standart error on input.\n"
/*...*/
printf(E_MS);
Если компилятор обнаруживает идентификатор E_MS, то он замещает его строкой «Standart error on input.» На самом деле компилятор увидит оператор в виде
printf("Standart error on input.\n");
Если идентификатор находится в строке, то подстановка не происходит. Например:
#define XYZ this is a test
/*...*/
printf("XYZ");
выведет не «this is a test», a «XYZ».
Если строка не вмещается в одной строке, то ее можно продолжить на следующей строке, поместив в конце строки обратный слэш, как показано в следующем примере:
#define LONG_STRING "This is a very long" \
string that is used as an example."
Программисты, пишущие на С, часто используют заглавные буквы для определения идентификаторов. Данное соглашение помогает любому человеку, читающему программу, бросив на нее один взгляд, узнать, что он имеет дело с макросом. Также вce #define лучше помещать в начале файла или вообще в отдельный заголовочный файл.
Очень часто макросы используют для определения «магических чисел», используемых в программе. Например, программа может определять массив и иметь несколько процедур для работы с ним. Вместо того, чтобы жестко кодировать размер массива, лучше определить макрос, соответствующий размеру массива, и использовать его в тех местах, где необходимо использование размера. Таким образом, если необходимо изменить размер массива, единственное, что требуется сделать, — это изменить оператор #define и перекомпилировать программу. Везде, где использовался данный макрос, произойдут автоматические изменения. Рассмотрим пример:
#define MAX_SIZE 100
/*...*/
float balance[MAX_SIZE];
/*...*/
float temp[MAX_SIZE];
Для изменения размеров обоих массивов просто изменим определение MAX_SIZE.
Директива #define имеет еще одну возможность: макрос может иметь аргументы. Каждый раз при встрече такого макроса аргументы макроса будут замещаться реальными аргументами программы. Такой тип макроса называется макрос типа функция. Например:
#include <stdio.h>
#define MIN(a,b) ((a)<(b)) ? (a) : (b)
int main(void)
{
int x, y;
x = 10; у = 20;
printf("The minimum is: %d", MIN(x, y) );
return 0;
}
При компиляции программы вместо MIN(a, b) подставляется указанное выражение, причем вместо фиктивных параметров а и b подставляются реальные х и у. Таким образом, в результате подстановки функция printf() примет следующий вид:
printf ("The minimum is: %d",((x) < (y) ) ? (x) : (y) );
Надо быть осторожным при определении макросов, получающих аргументы, или можно получить несколько неожиданные результаты. Например, рассмотрим следующую короткую программу, использующую макрос для определения четности значения:
/* программа выдает неправильный результат */
#include <stdio.h>
#define EVEN(a) a%2==0 ? 1 : 0
int main(void)
{
if (EVEN(9+1) ) printf("is even");
else printf ("is odd");
return 0;
}
Из-за способа подстановки данная программа работает неправильно. В результате компиляции программы EVEN(9 + 1) расширится до
9 + 1% 2 == 0 ? 1 : 0
Как известно, оператор взятия по модулю имеет более высокий приоритет, чем оператор сложения. Это означает, что сначала выполнится взятие по модулю с числом 1, а затем результат прибавится к 9, что, естественно, не может быть равно 0. Для устранения данной проблемы следует заключить а в макросе EVEN в круглые скобки, как показано в следующей правильной версии программы:
#include <stdio.h>
#define EVEN(a) (a)%2==0 ? 1 : 0
int main(void)
{
if(EVEN(9 + 1) ) printf("is even");
else printf("is odd");
return 0;
}
Обратим внимание, что 9+1 вычисляется до взятия по модулю. В целом заключение параметров макроса в скобки — это достаточно хорошая идея, и она позволяет избежать множества проблем.
Использование макроподстановок вместо реальных функций имеет одно большое преимущество — существенно увеличивается скорость работы программы, поскольку нет необходимости тратить время на вызов функции и возврат из нее. Тем не менее, за данное увеличение скорости работы следует платить увеличением размера исполнимого кода программы, поскольку программа вынуждена дублировать код макроса.