Использование аргументов с #define

В языке С способ, с помощью которого задается символическое имя, называется макроопределением. Макроопределение с аргументами очень похоже на функцию, поскольку аргументы его заключены в скобки. В отличие от функции здесь не задается тип аргумента, поскольку просто выполняется буквальная подстановка текста и не выполняется поверок, как для функции.

Пример:

#define abs(A) (((A) > 0)?(A) : -(A))

Каждое вхождение выражения abs(arg) в тексте программы заменяется на

((arg) > 0) ? (arg) : -(arg),

причем параметр макроопределения А заменяется на arg.

Пример:

/* макроопределение с аргументами */ #define SQUARE(x) x*x#define PR(x) printf("x равно %d.\n", x)main( ){ int x = 4; int z; z = SQUARE(x); PR(z); //16 z = SQUARE(2); PR(z); //4 PR(SQUARE(x)); //PR(16) =16 PR(SQUARE(x+2)); // PR(x+2*x+2)=14 PR(100/SQUARE(2)); // PR(100/2*2)=100 PR(SQUARE(++x)); //PR(++x*++x)=36}

Всюду, где в нашей программе появляется макроопределение SQUARE(x), оно заменяется на x*x. В отличие от наших прежних примеров, при использовании этого макроопределения мы можем совершенно свободно применять символы, отличные от x. В макроопределении ' x ' замещается символом, использованным в макровызове программы. Поэтому макроопределение SQUARE(2) замещается на 2*2. Таким образом, x действует как аргумент. Однако, аргумент макроопределения не работает - точно так же, как аргумент функции. Вот результаты выполнения программы:

z равно 16.z равно 4.SQUARE(x) равно 16.SQUARE(x+2) равно 14. 100/SQUARE(2) равно 100.SQUARE(++x) равно 36.

Первые две строки очевидны. Заметим, что даже внутри двойных кавычек в определении PR переменная замещается соответствующим аргументом. Все аргументы в этом определении замещаются. Рассмотрим третью строку:

PR(SQUARE(x));

Она становится следующей строкой:

printf("SQUARE(x) равно %d.\n", SQUARE(x));

после первого этапа макрорасширения. Второе SQUARE(x) расширяется, превращаясь в x*x, а первое остается без изменения, потому что теперь оно находится внутри кавычек в операторе программы, и таким образом защищено от дальнейшего расширения. Окончательно строка программы содержит

printf("SQUARE(x) равно %d.\n",x*x);

и выводит на печать

SQUARE(x) равно x*x.

Аргумент в кавычках препроцессорм не заменяется! Если макроопределение включает аргумент с двойными кавычками, то аргумент будет замещаться строкой из макровызова. Но после этого он в дальнейшем не расширяется, даже если строка является еще одним макроопределением. В нашем примере переменная x стала макроопределением SQUARE(x) и осталась им. Вспомним, что x=4. Это позволяет предположить, что SQUARE(x+2) будет равно 6*6 или 36. Но напечатанный результат говорит, что получается число 14. Причина такого результата такова: препроцессор не делает вычислений. Он только замещает строку. Всюду, где наше определение указывает на x, препроцессор подставит строку x+2.

Таким образом,

x*x становится x+2*x+2

Если x равно 4, то получается

4+2*4+2=4+8+2=14

 

Многие задачи можно решать, используя макроопределение с аргументами или функцию. Что из них следует применять? На этот счет нет строгих правил, но есть некоторые соображения.

Макроопределения должны использоваться скорее как хитрости, а не как обычные функции. Они могут иметь нежелательные побочные эффекты. Некоторые компиляторы ограничивают макроопределения одной строкой, и, по-видимому, лучше соблюдать такое ограничение, даже если ваш компилятор этого не делает.

Выбор макроопределения приводит к увеличению объема памяти, а выбор функции - к увеличению времени работы программы. Макроопределение создает строчный код, т.е. мы получаем оператор в программе. Если макроопределение применить 20 раз, то в программу вставится 20 строк кода. Если мы используем функцию 20 раз, то у нас будет только одна копия операторов функции. Однако управление программой следует передать туда, где находится функция, а затем вернуться в вызывающую программу, а на это потребуется больше времени, чем при работе со строчными кодами. Так что думайте, что выбирать!

Преимущество макроопределений заключается в том, что при их использовании нам не нужно беспокоиться о типах переменных, т.к. макроопределения имеют дело с символьными строками, а не с фактическими значениями. Tак наше макроопределение SQUARE(x) можно использовать одинаково хорошо с переменными типа int или float.

Итак:

Отличие от функций:

1. Вызов функции передает значение аргумента в функцию во время выполнения программы. Макровызов передает строку аргументов в программу до ее компиляции.

2. Так как выражение для вычисления макроопределения подставляется непосредственно в программу, то размер ее увеличивается. Но при этом не затрачивается время на вызов функции, которая может быть использована вместо макроопределения. То есть использование макроопределений повышает быстродействие программы.

Оператор #

Если разместить оператор # перед параметром в макроопределении, препроцессор создаст строковую константу из параметра при вызове макроопределния. Например, задав макроопределение как

#define str(x) #x

И сделав вызов макроопределения следующим образом:

str(testing)

получим строку “testing”, которую создал препроцессор. Поэтому следующий вызов функции printf

printf(str(Programming in C\n));

будет эквивалентен вызову

printf(“Programming in C\n”);

Препроцессор просто расставляет двойные кавычки вокруг аргумента. Двойные кавычки или обратная черта в аргументах препроцессором сохраняются, поэтому вызов:

str(“Hello!”)

преобразуется в “\”Hello!\””

Еще один пример использование оператора #:

define printint(var) printf(# var ” = %i\n”,var) //пробел между #и парам не обязат

Это макроопределение используется для отображения значений целочисленной переменной . Если переменная count является целочисленной переменной со значением 100, то утверждение

printint(count);

будет преобразовано следующим образом:

printf(“count” ” = %i\n”,count);

А после объединения строк:

printf(“count = %i\n”,count);

Таким образом оператор # позволяет создавать строковые константы для аргумента макроопределения.

Оператор ##

Этот оператор используется в макроопределениях для объединения двух лексем. Он ставится до или после имени параметра макроопределения. Препроцессор обрабатывает аргументы при вызове макроопределения и создает одну лексему из двух, между которыми находится оператор ##.

Предположим, что необходимо создать список переменных от х1 до х100. Можно создать макроопределение, в которое в качестве параметра будут передаваться целые значения от 1 до 100 и затем отображаться вместе с именем х:

х1,х2,..х100

#define printx(n) printf(“%d\n”,x##n)

При этом вызов

printx(20)

даст результат “x20”