Использование аргументов с #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”