Использование аргументов с #defineВо избежание ошибок при вычислении выражений параметры макроопределения необходимо заключать в скобки. #define идентификатор1 (идентификатор2, . . .) строка Пример: #define abs(A) (((A) > 0)?(A) : -(A)) Каждое вхождение выражения abs(arg) в тексте программы заменяется на ((arg) > 0) ? (arg) : -(arg), причем параметр макроопределения А заменяется на arg. Пример: #define nmem(P,N)\ (P) -> p_mem[N].u_long Символ \ продолжает макроопределение на вторую строчку. Это макроопределение уменьшает сложность выражения, описывающего массив объединений внутри структуры. Макроопределение с аргументами очень похоже на функцию, поскольку аргументы его заключены в скобки: /* макроопределение с аргументами */ #define SQUARE(x) x*x #define PR(x) printf("x равно %d.\n", x) int main( ) { int x = 4; int z; z = SQUARE(x); PR(z); z = SQUARE(2); PR(z); PR(SQUARE(x)); PR(SQUARE(x+2)); PR(100/SQUARE(2)); PR(SQUARE(++x)); return 0; } Всюду, где в нашей программе появляется макроопределение 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) равно 30. Первые две строки очевидны. Заметим, что даже внутри двойных кавычек в определении 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. Запомним!
Предположим, что мы разработали несколько макрофункций по своему усмотрению. Если мы пишем новую программу, мы не должны их переопределять. Нужно использовать директиву #include. Включение файла: #includeПеречень обозначений заголовочных файлов для работы с библиотеками компиляторов утвержден стандартом языка. Ниже приведены названия этих файлов, а также краткие сведения о тех описаниях и определениях, которые в них включены. Большинство описаний - прототипы стандартных функций, а определены в основном константы, например EOF, необходимые для работы с библиотечными функциями. assert.h - диагностика программ ctype.h - преобразование и проверка символов errno.h - проверка ошибок float.h - работа с вещественными данными limits.h - предельные значения целочисленных данных locale.h - поддержка национальной среды math.h - математические вычисления setjump.h - возможности нелокальных переходов signal.h - обработка исключительных ситуаций stdarg.h - поддержка переменного числа параметров stddef.h - дополнительные определения stdio.h - средства ввода-вывода stdlib.h - функции общего назначения (работа с памятью) string.h - работа со строками символов time.h - определение дат и времени В конкретных реализациях количество и наименование заголовочных файлов могут быть и другими. Например, в компиляторах дляMS-DOS активно используются заголовочные файлы mem.h, alloc.h, conio.h, dos.h и другие. В компиляторах Turbo C, Borland C++ для связи с графической библиотекой применяется заголовочный файл graphics.h. Командная строка #include может встречаться в любом месте программы, но обычно все включения размещаются в начале файла исходного текста. #include <имя_файла> Пример: #include <math.h> Препроцессор заменяет эту строку содержимым файла math.h. Угловые скобки означают, что файл math.h будет взят из некоторого стандартного каталога (обычно это /usr/include ). Текущий каталог не просматривается: #include "имя_файла" Пример: #include "ABC" Препроцессор заменяет эту строку содержимым файла ABC. Так как имя файла заключено в кавычки, то поиск производится в текущем каталоге (в котором содержится основной файл исходного текста). Если в текущем каталоге данного файла нет, то поискпроизводится в каталогах, определенных именем пути в опции -l препроцессора. Если и там нет файла, то просматривается стандартный каталог. В операционной системе UNIX угловые скобки сообщают препроцессору, что файл следует искать в одном или нескольких стандартных системных каталогах. Кавычки говорят ему, что сначала нужно смотреть в вашем каталоге или в каком-то другом, если вы определяете его именем файла, а затем искать в стандартных местах. В конкретных реализациях количество и наименования заголовочных файлов могут быть разными: #include <stdio.h> ищет в системном каталоге #include "my.h" ищет в текущем рабочем каталоге #include "/user/1/my.h" ищет в каталоге /user/1 В типичной микропроцессорной системе эти две формы являются синонимами, и препроцессор ведет поиск на указанном диске. #include "stdio.h" ищет на стандартном диске #include <stdio.h> ищет на стандартном диске #include "a:stdio.h" ищет на диске а По соглашению суффикс .h используется для заголовочных файлов, т.е. файлов с информацией, которая располагается в начале программы. Заголовочные файлы обычно состоят из операторов препроцессора. Некоторые файлы включены в систему, например, stdio.h, но можно создать и свой файл.
|