#include "filename"
заменяется содержимым файла с именем filename. (Кавычки обязательны). Часто одна или две строки такого вида появляются в начале каждого исходного файла, для того чтобы включить общие конструкции #define и описания extern для глобальных переменных. Допускается вложенность конструкций #include.
Конструкция #include является предпочтительным способом связи описаний в больших программах. Этот способ гарантирует, что все исходные файлы будут снабжены одинаковыми определениями и описаниями переменных, и, следовательно, исключает особенно неприятный сорт ошибок. Естественно, когда какой-то включаемый файл изменяется, все зависящие от него файлы должны быть перекомпилированы.
5.11.2. Макроподстановка. Определение вида:
#define tes 1
приводит к макроподстановке самого простого вида – замене имени на строку символов. Имена в #define имеют ту же самую форму, что и идентификаторы в «С»; заменяющий текст совершенно произволен. Нормально заменяющим текстом является остальная часть строки; длинное определение можно продолжить, поместив \ в конец продолжаемой строки. «Область действия» имени, определенного в #define, простирается от точки определения до конца исходного файла. Имена могут быть переопределены, и определения могут использовать определения, сделанные ранее. Внутри заключенных в кавычки строк подстановки не производятся, так что если, например, yes – определенное имя, то в printf("yes") не будет сделано никакой подстановки.
Так как реализация #define является частью работы макропредпроцессора, а не собственно компилятора, имеется очень мало грамматических ограничений на то, что может быть определено. Так, например, любители АЛГОЛА или ПАСКАЛЯ могут объявить:
#define begin {
#define end ;}
и затем написать
if (i > 0) then
begin
a = 1;
b = 2
end
Имеется также возможность определения макроса с аргументами, так что заменяющий текст будет зависеть от вида обращения к макросу.
Пример 5-10. Определим макрос с именем max следующим образом:
#define max(a, b) ((a) > (b) ? (a) : (b))
когда строка
x = max(p+q, r+s);
будет заменена строкой
x = ((p+q) > (r+s) ? (p+q) : (r+s));
Такая возможность обеспечивает «функцию максимума», которая расширяется в последовательный код, а не в обращение к функции. При правильном обращении с аргументами такой макрос будет работать с любыми типами данных; здесь нет необходимости в различных видах max для данных разных типов, как это было бы с функциями.
Конечно, если вы тщательно рассмотрите приведенное выше расширение max, вы заметите определенные недостатки. Выражения вычисляются дважды; это плохо, если они влекут за собой побочные эффекты, вызванные, например, обращениями к функциям или использованием операций увеличения. Нужно позаботиться о правильном использовании круглых скобок, чтобы гарантировать сохранение требуемого порядка вычислений.
Пример 5-11. Рассмотрим макрос
#define square(x) x*x
при обращении к нему, как square(z+1)). Здесь возникают даже некоторые чисто лексические проблемы: между именем макро и левой круглой скобкой, открывающей список ее аргументов, не должно быть никаких пробелов.
Тем не менее аппарат макросов является весьма ценным. Один практический пример дает описываемая в главе 9 стандартная библиотека ввода-вывода, в которой getchar и putchar определены как макросы (очевидно PUTCHAR должна иметь аргумент), что позволяет избежать затрат на обращение к функции при обработке каждого символа.
Другие возможности макропроцессора описаны в подробных справочниках.
Упражнение 5-9. Определите макрос swap(x,y), который обменивает значениями два своих аргумента типа int (в этом случае поможет блочная структура).
Существует еще один момент, о котором следует предупредить читателя, – определения и объявления совместно используются несколькими файлами. Мы бы хотели, насколько это возможно, централизовать эти объявления и определения так, чтобы для них существовала только одна копия. Тогда программу в процессе ее развития будет легче и исправлять, и поддерживать в нужном состоянии. Для этого общую информацию расположим в заголовочном файле calc.h, который будем по мере необходимости включать в другие файлы (строка #include была рассмотрен выше).
Неизбежен компромисс между стремлением, чтобы каждый файл владел только той информацией, которая ему необходима для работы, и тем, что на практике иметь дело с большим количеством заголовочных файлов довольно трудно. Для программ, не превышающих некоторого среднего размера, вероятно, лучше всего иметь один заголовочный файл, в котором собраны вместе все объекты, каждый из которых используется в двух различных файлах; так мы здесь и поступили. Для программ больших размеров потребуется более сложная организация с большим числом заголовочных файлов.
Указатель – это переменная, содержащая адрес другой переменной. Указатели очень широко используются в языке «C». Это происходит отчасти потому, что иногда они дают единственную возможность выразить нужное действие, а отчасти потому, что они обычно ведут к более компактным и эффективным программам, чем те, которые могут быть получены другими способами.
Указатели обычно смешивают в одну кучу с операторами goto, характеризуя их как «чудесный» способ написания программ, которые невозможно понять. Это, безусловно, справедливо, если указатели используются беззаботно; очень просто ввести указатели, которые указывают на что-то совершенно неожиданное. Однако, при определенной дисциплине, использование указателей помогает достичь ясности и простоты. Именно этот аспект мы попытаемся здесь проиллюстрировать.
Так как указатель содержит адрес объекта, это дает возможность "косвенного" доступа к этому объекту через указатель. Предположим, что х - переменная, например, типа int, а рх - указатель, созданный неким еще не указанным способом. Унарная операция & выдает адрес объекта, так что оператор
рх = &х;
присваивает адрес х переменной рх; говорят, что рх «указывает» на х. Операция & применима только к переменным и элементам массива, конструкции вида &(х-1) и &3 являются незаконными. Нельзя также получить адрес регистровой переменной.
Унарная операция * рассматривает свой операнд как адрес конечной цели и обращается по этому адресу, чтобы извлечь содержимое. Следовательно, если y тоже имеет тип int, то
y = *рх;
присваивает y содержимое того, на что указывает рх. Так последовательность
рх = &х;
y = *рх;
присваивает y то же самое значение, что и оператор
y = x;
Переменные, участвующие во всем этом необходимо описать:
int x, y;
int *px;
с описанием для x и y мы уже неодонократно встречались. Описание указателя
int *px;
является новым и должно рассматриваться как мнемоническое; оно говорит, что комбинация *px имеет тип int. Это означает, что если px появляется в контексте *px, то это эквивалентно переменной типа int. Фактически синтаксис описания переменной имитирует синтаксис выражений, в которых эта переменная может появляться. Это замечание полезно во всех случаях, связанных со сложными описаниями. Например,
double atof(), *dp;
говорит, что atof() и *dp имеют в выражениях значения типа double.
Вы должны также заметить, что из этого описания следует, что указатель может указывать только на определенный вид объектов. Указатели могут входить в выражения. Например, если px указывает на целое x, то *px может появляться в любом контексте, где может встретиться x. Так операторы
· y = *px + 1 присваивает y значение, на 1 большее значения x;
· printf("%d\n", *px) печатает текущее значение x;
· d = sqrt((double) *px) получает в d квадратный корень из x (причем до передачи функции sqrt значение x преобразуется к типу double, см. главу 3).
В выражениях вида
y = *px + 1
унарные операции * и & связаны со своим операндом более крепко, чем арифметические операции, так что такое выражение берет то значение, на которое указывает px, прибавляет 1 и присваивает результат переменной y. Мы вскоре вернемся к тому, что может означать выражение
y = *px + 1) .
Ссылки на указатели могут появляться и в левой части присваиваний. Если px указывает на x, то
*px = 0
полагает X равным нулю, а
*px += 1
увеличивает его на единицу, как и выражение
(*px)++
Круглые скобки в последнем примере необходимы; если их опустить, то поскольку унарные операции, подобные * и ++, выполняются справа налево, это выражение увеличит px, а не ту переменную, на которую он указывает.
И наконец, так как указатели являются переменными, то с ними можно обращаться, как и с остальными переменными. Если py – другой указатель на переменную типа int, то
py = px
копирует содержимое px в py, в результате чего py указывает на то же, что и px.