Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


Макросы в сравнении с функциями шаблонов



 

При работе с макросами и языке 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 а)

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

Затем использовать макрос fPRINT(x) с параметром Two, чтобы сгенерировать строку fTwoPrint, и с параметром Three, чтобы сгенерировать строку fThreePrint.

В конце второй недели обучения был разработан класс PartsList. Этот список мог обрабатывать объекты только типа List. Предположим, что этот список зарекомендовал себя хорошей работой и вам захотелось так же хорошо создавать списки животных, автомобилей, компьютеров и т.д.

Один метод решения этой задачи мог бы состоять в создании списков AnimalList, CarList, ComputerList и прочих путем вырезки и вставки кода в нужное место. Однако такой вариант решения быстро превратит вашу жизнь в кошмар, поскольку каждое изменение, вносимое в один список, нужно будет вносить во все другие.

Но, к счастью, существует альтернативное решение — использование макросов и оператора конкатенации. Например, можно определить следующий макрос:

#define Listof(Type) class Type##List

{

public:

Type##List(){ }

private:

int itsLength;

};

Суть этого примера состоит в том, чтобы включить в одно определение все необходимые методы и данные. Когда нужно будет создать список животных (AnimalList), достаточно записать

Listof(Animal)

и приведенная выше запись превратится в объявление класса AnimalList. В процессе применения этого подхода не обходится без некоторых проблем, подробно рассмотренных на занятии 19.

 

 

Встроенные макросы

 

Многие компиляторы используют ряд встроенных макросов, таких как DATE, __TIME__, __LINE__ и __FILE__. Каждое из этих имен окружено двумя символами подчеркивания, чтобы снизить вероятность того, что они войдут в противоречие с именами, использованными в вашей программе.

Когда препроцессор встречает один из этих макросов, он делает соответствующую подстановку. Вместо лексемы __DATE__ ставится текущая дата. Вместо __TIME__ — текущее время. Лексемы __LINE__ и __FILE__ заменяются номером строки исходного кода и именем файла соответственно. Следует отметить, что эти замены выполняются еще до компиляции. Учтите, что при выполнении программы вместо лексемы DATE будет стоять не текущая дата, а дата компиляции программы. Встроенные макросы часто используют при отладке.

 

Макрос assert()

 

Во многих компиляторах предусмотрен макрос assert, который возвращает значение TRUE, если его параметр принимает значение TRUE, и выполняет установленные действия, если его параметр принимает значение FALSE. Многие компиляторы в этом случае прерывают выполнение программы, другие же генерируют исключительную ситуацию (см. занятие 20).

Одна из важных особенностей макроса assert() состоит в том, что препроцессор вообще не замещает его никаким кодом, если не определена лексема DEBUG. Это свойство — большое подспорье в период разработки и при передаче заказчику конечного продукта. Быстродействие программы не страдает и размер исполняемой версии не увеличивается в результате использования этого макроса.

Чтобы не зависеть от конкретной версии компилятора, т.е. от его реакции на макрос assert(), можно написать собственный вариант этого макроса. В листинге 21.4 содержится простой макрос assert() и показано его использование.

Листинг 21.4. Простой макрос assert()

1: // Листинг 21.4. Макрос ASSERT

2: #define DEBUG

3: #include < iostream.h>

4:

5: #ifndef DEBUG

6: #define ASSERT(x)

7: #else

8: #define ASSERT(x)

9: if (! (x))

10: {

11: cout < < " ERROR!! Assert " < < #x < < " failed\n"; \

12: cout < < " on line " < < __LINE__ < < " \n"; \

13: cout < < " in file " < < FILE < < " \n"; \

14: }

15: #endif

16:

17:

18: int main()

19: {

20: int x = 5;

21: cout < < " Первый макрос assert: \n";

22: ASSERT(x==5);

23: cout < < " \nВторой макрос assert: \n";

24: ASSERT(x! = 5);

25: cout < < " \nВыполненоД n";

26: return 0:

27: }

 

Результат:

First assert:

Second assert:

ERROR!! Assert x! =5 failed

on line 24

in file test1704.cpp

Done.

 

Анализ: В строке 2 определяется лексема DEBUG. Обычно это делается из командной строки (или в интегрированной среде разработки) во время компиляции, что позволяет управлять этим процессом. В строках 8-14 определяется макрос assert(). Как правило, это делается в файле заголовка ASSERT.hpp, который следует включить во все файлы источников.

В строке 5 проверяется определение лексемы DEBUG. Если она не определена, макрос assert() определяется таким образом, чтобы вообще не создавался никакой код. Если же лексема DEBUG определена, то выполняются строки кода 8-14.

Сам макрос assert() представляет собой цельное выражение, разбитое на семь строк исходного кода. В строке 9 проверяется значение, переданное как параметр. Если передано значение FALSE, выводится сообщение об ошибках (строки 11 — 13). Если передано значение TRUE, никакие действия не выполняются.


Поделиться:



Популярное:

  1. Бенжамин Констан. О свободе у древних в ее сравнении со свободой у современных людей (1819).
  2. Включение в шаблон других шаблонов
  3. Выученные психиатрические синдромы в сравнении с психопатологическими
  4. Глава 3. ПРИНЦИПЫ ОРГАНИЗАЦИИ УПРАВЛЕНИЯ ФУНКЦИЯМИ
  5. Избавляйтесь от негативных шаблонов
  6. МАКРОСЫ КАК СРЕДСТВО АВТОМАТИЗАЦИИ РАБОТЫ
  7. Макросы. Назначение и алгоритм работы.
  8. Не хватит ли формул, шаблонов, моделей, бумажек и прочих инструкций от «компетентных источников»? Куда нам столько эгрегоров и привязок, где в этом всем свобода мысли и воли, где творчество?
  9. Оценка конкурентоспособности в сравнении с аналогом
  10. Под функциями муниципального права следует понимать основные направления воздействия норм на общественные отношения, складывающиеся в системе местного самоуправления.
  11. Создание презентаций на основе пустых шаблонов


Последнее изменение этой страницы: 2017-03-08; Просмотров: 716; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.029 с.)
Главная | Случайная страница | Обратная связь