Стивен Прата - Язык программирования C. Лекции и упражнения (6-е изд.) 2015
- Название:Язык программирования C. Лекции и упражнения (6-е изд.) 2015
- Автор:
- Жанр:
- Издательство:Вильямс
- Год:0101
- ISBN:нет данных
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Стивен Прата - Язык программирования C. Лекции и упражнения (6-е изд.) 2015 краткое содержание
Язык программирования C. Лекции и упражнения (6-е изд.) 2015 - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Макрос Тело замены
Рис. 16,2. Части определения функционального макроса
668 Глава 16
Вот пример определения:
#define SQUARE(X) Х*Х
Оно может применяться в программе следующим образом:
z = SQUARE(2);
Оператор выглядит похожим на вызов функции, хотя поведение макроса не обязательно будет идентичным. В листинге 16.2 иллюстрируется использование этого и второго макроса. В некоторых примерах также обращается внимание на возможные ловушки, поэтому читайте их внимательно.
Листинг 16.2. Программа mac arg.c
Макрос SQUARE имеет следующее определение:
#define SQUARE(X) Х*Х
Здесь SQUARE — идентификатор макроса, X в SQUARE (X) — аргумент макроса, а Х*Х — список замены. Каждое вхождение SQUARE (х) в листинге 16.2 заменяется х*х. Отличие данного примера от предыдущих состоит в возможности использования в макросе любых символов помимо X. Символ X в определении макроса заменяется символом, который указан при вызове макроса в программе. Таким образом, SQUARE (2) заменяется 2*2, так что X действительно играет роль аргумента.
Однако, как вскоре будет показано, аргумент макроса не работает в точности как аргумент функции. Ниже представлены результаты выполнения программы. Обратите внимание, что некоторые вычисления дают результат, отличающийся от того, что можно было ожидать. На самом деле ваш компилятор может даже выдать не такой результат, как приведенный в предпоследней строке:
Препроцессор и библиотека С 669
х = 5
Вычисление SQUARE(x): Результат: 25.
Вычисление SQUARE(2): Результат: 4.
Вычисление SQUARE(x+2): Результат: 17.
Вычисление 100/SQUARE(2): Результат: 100.
х = 5.
Вычисление SQUARE(++x): Результат: 42.
После инкрементирования х = 7.
Первые две строки вполне предсказуемы, но затем встречается несколько странных результатов. Вспомните, что х имеет значение 5. Это может привести к предположению, что SQUARE (х+2) должно быть 7*7, или 49, но выводится 17 — простое число, но определенно не квадрат! Причина такого вводящего в заблуждение вывода связана с тем, что, как уже говорилось, препроцессор не выполняет вычислений, а просто заменяет последовательности символов. Где бы в определении не появлялось х, препроцессор подставит вместо х символы х+2. Поэтому
х*х
принимает вид:
х+2*х+2
Единственным умножением является 2*х. Если х равно 4, выражение вычисляется следующим образом:
4 + 2*4 + 2 = 4 + 8 + 2 = 14
Этот пример подчеркивает важное отличие между вызовом функции и вызовом макроса. При вызове функции ей передается значение аргумента во время выполнения программы. При вызове макроса лексема аргумента передается в программу перед компиляцией; это другой процесс, происходящий в другое время. Можно ли исправить определение, чтобы вызов SQUARE (х+2) выдавал 36? Конечно. Нужны просто дополнительные круглые скобки:
#define SQUARE(х) (х)*(х)
Теперь SQUARE (х+2) превращается в (х+2) * (х + 2) , и вы получите ожидаемое умножение, поскольку круглые скобки останутся в заменяющей строке.
Однако это не решает всех проблем. Рассмотрим события, которые приводят к тому, что следующая строка:
100/SQUARE(2)
в выводе преобразуется к виду
100/2*2
Согласно приоритетам операций, выражение вычисляется слева направо: (100/2) *2, или 50*2, или 10 0. Для устранения путаницы SQUARE (х) необходимо определить так:
#define SQUARE(х) (х*х)
В результате это дает 100/ (2*2), что в итоге вычисляется как 100/4, или 25.
В следующем определении учтены ошибки обоих примеров:
#define SQUARE(х) ((х)*(х))
Из всего продемонстрированного можно извлечь такой урок: применяйте столько круглых скобок, сколько необходимо для того, чтобы обеспечить корректный порядок выполнения операций.
670 глава 16
Но даже эти меры предосторожности не спасают от ошибки в последнем примере:
SQUARE(++х)
В результате получается:
+ + Х* + +х
Здесь х инкрементируется дважды — один раз до операции умножения и один раз после нее:
++х*++х = 6*7 = 42
Из-за того, что выбор конкретного порядка выполнения операций оставлен за разработчиками реализаций, некоторые компиляторы генерируют умножение 7*6. Есть компиляторы, которые могут инкрементировать оба операнда перед умножением, выдавая в результате 7*7, или 49. На самом деле вычисление этого выражения приводит к ситуации, которая в стандарте называется неопределенным поведением. Тем не менее, во всех этих случаях х начинает со значения 5 и заканчивает значением 7, хотя код выглядит так, будто инкрементирование происходит только один раз.
Простейшее решение этой проблемы — избегать использования ++х как аргумента макроса. Вообще лучше не применять в макросах операции инкремента и декремента. Следует отметить, что выражение++х будет работать в качестве аргумента функции, т.к. оно вычисляется как значение 6, которое затем передается функции.
Создание строк из аргументов макроса: операция #
Рассмотрим следующий функциональный макрос:
#define PSQR(X) printf("Квадрат X равен %d.\n", ((X)*(X)));
Предположим, что этот макрос используется следующим образом:
PSQR(8);
Вот каким будет вывод:
Квадрат X равен 64.
Обратите внимание, что определение X в строке, заключенной в двойные кавычки, трактуется как обычный текст, а не лексема, которую можно заменить.
Представим, что вы хотите поместить аргумент макроса в строку. Язык С позволяет сделать это. Внутри заменяющей части функционального макроса символ # становится операцией препроцессора, которая преобразует лексемы в строки. Пусть х является параметром макроса, тогда #х — это имя параметра, преобразованное в строку "х". Такой процесс называется превращением в строку и демонс трируется в листинге 16.3.
Листинг 16.3. Программа subst.c
Препроцессор и библиотека С 671
Вывод выглядит следующим образом:
Квадрат у равен 25.
Квадрат 2+4 равен 36.
В первом вызове макроса #х заменяется строкой "у", а во втором вызове вместо #х подставляется "2 + 4". Конкатенация строк ANSI С затем объединяет эти строки с другими строками в операторе printf() для получения финальной строки. Например, первый вызов макроса дает следующий оператор:
printf("Квадрат " "у" " равен %d.\n",((у)*(у)));
После этого конкатенация объединяет три расположенные рядом строки в одну:
"Квадрат у равен %d.\n"
Средство слияния препроцессора: операция ##
Подобно #, операция ## может применяться в заменяющей части функционального макроса. Вдобавок она может использоваться в заменяющей части объектного макроса. Операция ## объединяет две лексемы в одну. Предположим, вы могли бы записать такое определение:
Читать дальшеИнтервал:
Закладка: