На этом шаге мы проиллюстрируем некоторые применения директив препроцессора.
#include <iostream.h> #define MAX(X,Y) ((X)>(Y)?(X):(Y)) void main () { int x,y; cout << "Задайте 2 целых числа: "; cin >> x >> y; cout << "Большее из них: " << MAX(x,y); }
#include <iostream.h> #include <stdio.h> #define PR(format,value) printf(format,value) #define NL putchar('\n') #define PRINT1(f,x1) PR(f,x1),NL #define MAX(X,Y) ((X)>(Y)?(X):(Y)) void main () { int x,y; cout << "Задайте 2 целых числа: "; cin >> x >> y; PRINT1 ("%d",MAX(x,y)); }
#include <iostream.h> #define S(x) x*x #define P(x) cout << "x равен " << x << endl void main () { int x=4; int z = S(x); P(z); z = S(2); P(z); P(S(x)); P(S(x+2)); P(100/S(2)); P(S(++x)); }
Результат работы программы:
x равен 16 x равен 4 x равен 16 x равен 14 x равен 100 x равен 36
#include <iostream.h> #define FUDGE(k) k+3 #define PR(a) cout << (int)(a) << "\t" #define PRINT(a) PR(a); cout << endl #define PRINT2(a,b) PR(a); PRINT(b) #define PRINT3(a,b,c) PR(a); PRINT2(b,c) #define MAX(a,b) (a < b ? b:a) void main() { { int x = 2; x = x * FUDGE(x); PRINT (x); /* Препроцессор 1 */ } { int cel; for (cel=0; cel<=100; cel+=50) PRINT2 (cel,9/5*cel+32); /* Препроцессор 2 */ } { int x,y; x = 1; y = 2; PRINT3 (MAX(x++,y),x,y); PRINT3 (MAX(x++,y),x,y); /* Препроцессор 3 */ } }
Результаты работы программы:
7 0 50 100 182 2 2 2 3 4 2
Комментарии.
Препроцессор 1.
int x = 2; Переменная x получает значение 2. x = x * FUDGE (x); т.е. x = 2 * FUDGE (2);
Чтобы понять эффект макроподстановки, нужно "провести" ее в месте вызова. Вместо FUDGE(2) подставим 2+3 (без скобок!). Получится x = 2 * 2 + 3;. Заменяя формальный параметр на фактический, получаем неожиданный результат. Сначала умножаем, затем складываем.
PRINT (x); Производится макроподстановка: PR (x); cout << endl Опять производится макроподстановка, на этот раз PR. cout << (int)(x) << "\t"; cout << endl
Нежелательного взаимодействия между строкой-подстановкой и контекстом в приведенной выше задаче могло бы и не быть, если бы FUDGE(k) определялось как (k+3).
Препроцессор 2.
for (cel=0; cel<=100; cel+=50) PRINT2 (cel,9/5*cel+32); for (cel=0; cel<=100; cel+=50) Вначале производим макропод- PR (cel); становку PRINT2. PRINT (9/5*cel+32); for (cel=0; cel<=100; cel+=50) Затем производим макропод- cout << (int)(cel) << "\t"; становку PR. PRINT (9/5*cel+32); for (cel=0; cel<=100; cel+=50) Производим макроподстановку cout << (int)(cel) << "\t"; PRINT. PR (9/5*cel+32); cout << endl; for (cel=0; cel<=100; cel+=50) Производим макроподстановку cout << (int)(cel) << "\t"; PR. cout << (int)(9/5*cel+32) << "\t"; cout << endl;
Обращение к PRINT2 выглядит как один оператор, но после макроподстановки появляются три. Только первое обращение к PR происходит после выполнения цикла for со значением cel = 150.
В данной задаче, чтобы удовлетворить этому правилу, надо использовать в теле PRINT вместо точек с запятой запятые.
Препроцессор 3.
int x=1,y=2; PRINT3 (MAX(x++,y),x,y);
Макроподстановка PRINT3 происходит прежде MAX. Однако, чтобы не затемнять сути, в этой и последующей задачах подстановка PRINT не будет производиться. Тогда первый шаг состоит в подстановке строки вместо MAX.
(x++ < y ? y: x++),x,y Затем заменяем аргументы макроподста- новки на фактические. (1 < 2 ? y: x++) Наконец производим вычисления. Ответ: 2 (т.е. y), а x получает значе- ние 2 в результате операции x++. PRINT3(MAX(x++,y),x,y); Теперь выполняем второе обращение к PRINT3. (x++ < y ? y : x++),x,y (2 < 2 ? y : x++) Ответ: 3 (т.е. x++), а x получает зна- чение 4 в результате двух операций x++.
При обращении x++ появляется только один раз, но в расширении фигурирует уже два раза, что приводит к увеличению x иногда на 1 и иногда на 2. Тяжесть ответственности за защиту от таких нежелательных побочных эффектов можно возлагать или на того, кто определяет макроподстановку, или на того, кто ею пользуется.
В общем случае проблема побочных эффектов достаточно хитрая. Следование правилу 3 часто означает копирование аргументов в локальные переменные подстановки; эта добавочная работа уменьшает преимущество (в скорости) макроподстановок перед обращением функциям. Следование же правилу 3А требует знания того, какая подпрограмма реализована как макроподстановка, а какая как функция. В лучшем случае это нарушает представление о подпрограмме как некой абстракции, а в худшем случае может привести к ошибке, если данная подпрограмма будет реализована по-другому.
В данной задаче следование правилу 3А не затронет саму MAX.
#include <iostream.h> #define NEG(a) -a #define weeks(mins) (days(mins)/7) #define days(mins) (hours(mins)/24) #define hours(mins) (mins/60) #define mins(secs) (secs/60) #define TAB(c,i,oi,t) if(c=='\t')\ for(t=8-(i-oi-1)%8,oi=i;t;t--)\ cout << " " #define PR(a) cout << (int)(a) << "\t" #define PRINT(a) PR(a); cout << endl void main () { { int x=1; PRINT(-NEG(x)); /* Препроцессор 1 */ } { PRINT (weeks(10080)); PRINT (days(mins(86400))); /* Препроцессор 2 */ } { static char input[]="\twhich\tif?"; char c; int i,oldi,temp; /* ------------------------------------- */ for (oldi=-1,i=0; (c=input[i])!='\0'; i++) if (c< ' ') TAB (c,i,oldi,temp); else cout << c; cout << "#\n"; /* Препроцессор 3 */ cout << "123456789ABCD\n"; } }
Результаты работы программы:
0 1 1 # 123456789ABCD
Комментарии.
Препроцессор 1.
int x=1; PRINT(-NEG(x)); NEG(x) заменяется на -x, что приводит к появ- лению оператора PRINT(--x);. Далее вычисляет- ся значение выражения --x, которое, в силу инициализации x, равно 0. Дальнейшее очевид- но.
Если, следуя правилу 1, определить NEG(a) как (-а), то получим иной результат (подумайте, какой?).
Препроцессор 2.
PRINT(weeks(10080)); Заменяем каждое обращение на соот- PRINT(days(10080)/7); ветствующую строку. Заметим, что здесь нет никакого конфликта между параметром mins и именем макроподс- тановки mins. PRINT((hours(10080)/24)/7); PRINT(((10080/60)/24)/7); Вычисляем. Аналогично: PRINT(days(mins(86400))); PRINT(hours(mins(86400))/24); PRINT((mins(86400)/60)/24); PRINT(((86400/60)/60)/24); Вычисляем.
Препроцессор 3.
static char input[]="\twhich\tif?"; if (c< ' ') TAB (c,i,oldi,temp); else cout << c;
if (c<' ') if (c=='\t') for (temp=8-(i-oldi-1)%8,oldi=i;temp;temp--) cout << " "; else cout << c;
TAB включает "открытый" условный оператор (не содержащий части else), поэтому после подстановки он "поглощает" ближайшую else-часть.
В предыдущей задаче трудность устраняется добавлением в определение TAB "пустой" else-части.
На следующем шаге мы рассмотрим директиву включения файлов.