Вторая проблема состоит в том, что макросы выполняются путем подстановки их выражений в код программы при каждом вызове. Это означает, что если макрос используется 12 раз, то столько же раз н вашу программу будет вставлено соответствующее выражение (вместо одного раза, как при обращении к обычной функции). Хотя, с другой стороны, подставляемые выражения обычно работают быстрее, чем вызовы функций, поскольку не тратится время па само обращение к функции.
Тот факт, что макросы выполняются путем подстановки выражений в код программы, приводит к третьей проблеме, которая проявляется в том, что макросы отсутствуют в исходном коде программы, используемом компилятором для ее тестирования. Это может существенно затруднить отладку программы.
Однако наиболее существенна последняя проблема: в макросах не поддерживается контроль за соответствием типов данных. Хотя возможность использования в макросе абсолютно любого параметра кажется удобной, этот факт полностью подрывает строгий контроль типов в C++ и является проклятием для программистов на C++. Конечно, существует корректный способ решить и эту проблему — нужно воспользоваться услугами шаблонов, как было показано на занятии 19.
Часто вместо макросов удобно объявить подставляемую функцию. Например, в листинге 21.3 создается функция CUBE, которая выполняет ту же работу, что и макрос CUBE в листинге 21.2, но в данном случае это делается способом, обеспечивающим контроль за соответствием типов.
Листинг 21.3. Использование подставляемой функции вместо макроса
1: #include
2:
3: inline unsigned long Square(unsigncd long а) { return а * а; }
4: inline unsigned long Cubo(unsigned long а)
5: { return а * а * а; }
6: int main()
7: {
8: unsigned long x=1 ;
9: for (;;)
10: {
11: cout << "Enter а number (0 to quit): ";
12: cin >> x;
13: if (x == 0)
14: break;
15: cout << "You entered: " << x;
16: cout << ". Square(" << x << "): ";
17: cout << Square(x);
18: cout<< ". Cube(" << x << "): ";
19: cout << Cube(x) << "." << endl;
20: }
21: return 0;
22: }
Результат:
Enter а number (0 to quit) 1
You ent.erod: 1. Square(1) 1. Cube(1): 1.
Enter а number (0 t.o quit) 2
You entered: 2. Square(2) 4. Cube(2): 8
Enter a number (0 t.o quit.) 3
You enlered: 3. Square(3) 9. Cube(3): 27.
Enter a number (0 to quit) 4
You entered: 4. Squate(4) 16 Cube(4) 64.
Enter a number (0 to quit) 5
You entered: 5, Squate(5) 25 Cubo(5) 125
Enter a number (0 to qu.it) 6
You entered: 6. Squaro(6) 36 Cube(6) 216
Enter a number (0 to quit) 0
Анализ:В строках 3 и 4 определяются две подставляемые функции: Square() и Cube(). Поскольку обе функции объявлены подставляемыми с помошью ключевого слова inlino, они, как и макросы, будут вставлены в код программы по месту каждого вызова, и никаких временных затрат при выполнении программы, связанных с обращениями к функциям, не возникнет.
Напомним, что подставляемые функции помещаются во время компиляции в программу всюду, где делается обращение к функции (например, в строке 17). А поскольку реального вызова функции никогда не происходит, отсутствуют и временные затраты, связанные с помещением в стек адреса возврата и параметров функции.
В строке 17 вызывается функция Square, а в строке 19 — функция Cube. И вновь-таки, поскольку эти функции подставляемые, реально строка их вызова после компиляции будут выглядеть следующим образом:
16: cout << ". Square(" << x << "): " << x * x << ". Cube (" << x << "): " << x * x * x << "." << endl;
Препроцессор предоставляет два специальных оператора для управления строками в макросах. Оператор взятия в кавычки (#) берет в кавычки любую строку, которая следует за ним. Оператор конкатенации (##) объединяет две строки в одну.
Оператор взятия в кавычки
Этот оператор берет в кавычки любые следующие за ним символы вплоть до очередно символа пробела. Следовательно, если написать
#define WRITESTRING(x) cout << #x
и выполнить следующий вызов макроса:
WRITESTRING(This is а string);
то препроцессор превратит его в такую строку кода:
cout << "This is а string";
Обратите внимание, что строка This is а string заключается в кавычки, что и требуется для объекта cout.
Оператор конкатенации позволяет связывать несколько строк в одну. Новая строка на самом деле представляет собой лексему, которую можно использовать как имя класса, имя переменной, смещение в массиве или другом объекте, где может содержаться ряд символов.
Предположим на мгновение, что у вас есть пять функций с такими именами, как fOnePrint, fTwoPrint, fThreePrint, fFourPrint и fFivePrint. Теперь можно сделать следующее объявление:
#define fPRINT(x) f ## x ## Print
Читать дальше