Освой самостоятельно С++ за 21 день.
Шрифт:
z = MAX(x,y);
то промежуточный код будет иметь следующий вид:
int x = 5, у = 7, z;
z = (x,y) ( (x) > (у) ? (x) : (у) )(x,y)
В этом случае сделана простая текстовая замена, а не вызов макроса, т.е. лексема МАХ была заменена выражением (x,y) ( (x) > (у) ? (x) : (у) ),за которым сохранилась строка (x, у).
Однако после удаления пробела между словом МАХ и списком параметров (x,y) промежуточный код выглядит уже по-другому:
int x = 5, у = 7, z;
z =7;
Зачем
Вам может показаться странным, что в макросах используется так много круглых скобок. На самом деле препроцессор совсем не требует, чтобы вокруг параметров в строке подстановки ставились круглые скобки, но эти скобки помогают избежать нежелательных побочных эффектов при передаче макросу сложных значений. Например, если определить МАХ как
#define MAX(x,y) x > у ? x : у
и передать значения 5 и 7, то макрос МАХ будет нормально работать. Но если передать более сложные выражения, можно получить неожиданные результаты, как показано в листинге 21.2.
Листинг 21.2. Использование в макросе круглых скобок
1: // Листинг 21.2. Использование в макросе круглых скобок
2: #include <iostream.h>
3:
4: #define CUBE(a) ( (а) * (а) << (а) )
5: #define THREE(a) а * а * а 6:
7: int main
8: {
9: long x = 5;
10: long у = CUBE(x);
11: long z = THREE(x);
12:
13: cout << "у: " << у << endl;
14: cout << "z: " << z << endl;
15:
16: long а = 5, b = 7;
17: у = CUBE(a+b);
18: z = THREE(a+b);
19:
20: cout << "у: " << у << endl;
21: cout << "z: " << z << endl;
22: return 0;
23: }
Результат:
у: 125
z: 125
у: 1728
z: 82
Анализ: В строке 4 определяется макрос CUBE с параметром x, который заключается в круглые скобки при каждом его использовании в выражении. В строке 5 определяется макрос THREE, параметр которого используется без круглых скобок.
При первом использовании этих макросов параметру передается значение 5, и оба макроса прекрасно справляются со своей работой. Макрос CUBE(5) преобразуется в выражение ( (5) * (5) * (5) ), которое при вычислении дает значение 125, а макрос THREE(5) преобразуется в выражение 5 * 5 * 5, которое также возвращает значение 125.
При повторном обращении к этим макросам в строках 16—18 параметру передается выражение 5 + 7. В этом случае макрос CUBE(5+7) преобразуется в следующее выражение:
( (5+7) * (5+7) * (5+7) )
Оно соответствует выражению
( (12) * (12) * (12) )
При вычислении этого выражения получаем значение 1728. Однако макрос THREE(5+7) преобразуется в выражение иного вида:
5 + 7 * 5 + 7 * 5 + 7
А поскольку операция умножения имеет более высокий приоритет по сравнению с операцией сложения, то предыдущее выражение эквивалентно следующему:
5 + (7 * 5) + (7 * 5) + 7
После вычисления произведений в круглых скобках получаем выражение
5 + (35) + (35) + 7
После суммирования оно возвращает значение 82.
Макросы в сравнении с функциями шаблонов
При работе с макросами и языке C++ можно столкнуться с четырьмя проблемами. Первая состоит в возможных неудобствах при увеличении самого выражения макроса, поскольку любой макрос должен быть определен в одной строке. Безусловно, эту строку можно продлить с помощью символа обратной косой черты (\), но большие макросы сложны для понимания и с ними трудно работать.
Вторая проблема состоит в том, что макросы выполняются путем подстановки их выражений в код программы при каждом вызове. Это означает, что если макрос используется 12 раз, то столько же раз н вашу программу будет вставлено соответствующее выражение (вместо одного раза, как при обращении к обычной функции). Хотя, с другой стороны, подставляемые выражения обычно работают быстрее, чем вызовы функций, поскольку не тратится время па само обращение к функции.
Тот факт, что макросы выполняются путем подстановки выражений в код программы, приводит к третьей проблеме, которая проявляется в том, что макросы отсутствуют в исходном коде программы, используемом компилятором для ее тестирования. Это может существенно затруднить отладку программы.
Однако наиболее существенна последняя проблема: в макросах не поддерживается контроль за соответствием типов данных. Хотя возможность использования в макросе абсолютно любого параметра кажется удобной, этот факт полностью подрывает строгий контроль типов в C++ и является проклятием для программистов на C++. Конечно, существует корректный способ решить и эту проблему — нужно воспользоваться услугами шаблонов, как было показано на занятии 19.
Подставляемые функции
Часто вместо макросов удобно объявить подставляемую функцию. Например, в листинге 21.3 создается функция CUBE, которая выполняет ту же работу, что и макрос CUBE в листинге 21.2, но в данном случае это делается способом, обеспечивающим контроль за соответствием типов.
Листинг 21.3. Использование подставляемой функции вместо макроса
1: #include <iostream.h>
2:
3: inline unsigned long Square(unsigncd long а) { return а * а; }
4: inline unsigned long Cubo(unsigned long а)