![]() Заглавная страница Избранные статьи Случайная статья Познавательные статьи Новые добавления Обратная связь FAQ Написать работу КАТЕГОРИИ: ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ТОП 10 на сайте Приготовление дезинфицирующих растворов различной концентрацииТехника нижней прямой подачи мяча. Франко-прусская война (причины и последствия) Организация работы процедурного кабинета Смысловое и механическое запоминание, их место и роль в усвоении знаний Коммуникативные барьеры и пути их преодоления Обработка изделий медицинского назначения многократного применения Образцы текста публицистического стиля Четыре типа изменения баланса Задачи с ответами для Всероссийской олимпиады по праву ![]() Мы поможем в написании ваших работ! ЗНАЕТЕ ЛИ ВЫ?
Влияние общества на человека
Приготовление дезинфицирующих растворов различной концентрации Практические работы по географии для 6 класса Организация работы процедурного кабинета Изменения в неживой природе осенью Уборка процедурного кабинета Сольфеджио. Все правила по сольфеджио Балочные системы. Определение реакций опор и моментов защемления |
Зачем нужны все эти круглые скобкиСодержание книги
Поиск на нашем сайте
Вам может показаться странным, что в макросах используется так много круглых скобок. На самом деле препроцессор совсем не требует, чтобы вокруг параметров в строке подстановки ставились круглые скобки, но эти скобки помогают избежать нежелательных побочных эффектов при передаче макросу сложных значений. Например, если определить МАХ как #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 а) 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, никакие действия не выполняются.
|
||||||||
Последнее изменение этой страницы: 2016-12-10; просмотров: 409; Нарушение авторского права страницы; Мы поможем в написании вашей работы! infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.224.58.21 (0.012 с.) |