Препроцессор Си - это программа, которая обрабатывает входные данные для компилятора. Препроцессор просматривает исходную программу и выполняет следующие действия: подключает к ней заданные файлы, осуществляет подстановки, а также управляет условиями компиляции. Для препроцессора предназначены строки программы, начинающиеся с символа #. В одной строке разрешается записывать только одну команду (директиву препроцессора).
Директива
#define идентификатор подстановка
вызывает замену в последующем тексте программы названного идентификатора на текст подстановки (обратите внимание на отсутствие точки с запятой в конце этой команды). По существу, эта директива вводит макроопределение (макрос), где "идентификатор" - это имя макроопределения, а "подстановка" - последовательность символов, на которые препроцессор заменяет указанное имя, когда находит его в тексте программы. Имя макроопределения принято набирать прописными буквами.
Рассмотрим примеры:
#define MAX 25
#define BEGIN {
|
|
Первая строка вызывает замену в программе идентификатора MAX на константу 25. Вторая позволяет использовать в тексте вместо открывающей фигурной скобки ({) слово BEGIN.
Отметим, что поскольку препроцессор не проверяет совместимость между символическими именами макроопределений и контекстом, в котором они используются, то рекомендуется такого рода идентификаторы определять не директивой #define, а с помощью ключевого слова const с явным указанием типа (это в большей степени относится к Си++):
const int MAX = 25;
(тип int можно не указывать, так как он устанавливается по умолчанию).
Если директива #define имеет вид:
#define идентификатор(идентификатор,..., идентификатор) подстановка
причем между первым идентификатором и открывающей круглой скобкой нет пробела, то это определение макроподстановки с аргументами. Например, после появления строки вида:
#define READ(val) scanf("%d", &val)
оператор READ(y); воспринимается так же, как scanf("%d",&y);. Здесь val - аргумент и выполнена макроподстановка с аргументом.
При наличии длинных определений в подстановке, продолжающихся в следующей строке, в конце очередной строки с продолжением ставится символ \.
В макроопределение можно помещать объекты, разделенные знаками ##, например:
#define PR(x, у) x##y
После этого PR(а, 3) вызовет подстановку а3. Или, например, макроопределение
#define z(a, b, c, d) a(b##c##d)
приведет к замене z(sin, x, +, y) на sin(x+y).
Символ #, помещаемый перед макроаргументом, указывает на преобразование его в строку. Например, после директивы
#define PRIM(var) printf(#var"= %d", var)
следующий фрагмент текста программы
year = 2006;
PRIM(year);
преобразуется так:
|
|
year = 2006;
printf("year""= %d", year);
Опишем другие директивы препроцессора. Директива #include уже встречалась ранее. Ее можно использовать в двух формах:
#include "имя файла"
#include <имя файла>
Действие обеих команд сводится к включению в программу файлов с указанным именем. Первая из них загружает файл из текущего или заданного в качестве префикса каталога. Вторая команда осуществляет поиск файла в стандартных местах, определенных в системе программирования. Если файл, имя которого записано в двойных кавычках, не найден в указанном каталоге, то поиск будет продолжен в подкаталогах, заданных для команды #include <...>. Директивы #include могут вкладываться одна в другую.
Следующая группа директив позволяет избирательно компилировать части программы. Этот процесс называется условной компиляцией. В эту группу входят директивы #if, #else, #elif, #endif, #ifdef, #ifndef. Основная форма записи директивы #if имеет вид:
#if константное_выражение последовательность_операторов
#endif
Здесь проверяется значение константного выражения. Если оно истинно, то выполняется заданная последовательность операторов, а если ложно, то эта последовательность операторов пропускается.
Действие директивы #else подобно действию команды else в языке Си, например:
#if константное_выражение
последовательность_операторов_1
#else
последовательность_операторов_2
#endif
Здесь если константное выражение истинно, то выполняется последовательность_операторов_1, а если ложно - последовательность_операторов_2.
Директива #elif означает действие типа "else if". Основная форма ее использования имеет вид:
#if константное_выражение
последовательность_операторов
#elif константное_выражение_1
последовательность_операторов_1
#elif константное_выражение_n
последовательность_операторов_n
#endif
Эта форма подобна конструкции языка Си вида: if...else if...else if...
Директива
#ifdef идентификатор
устанавливает определен ли в данный момент указанный идентификатор, т.е. входил ли он в директивы вида #define. Строка вида
#ifndef идентификатор
проверяет является ли неопределенным в данный момент указанный идентификатор. За любой из этих директив может следовать произвольное число строк текста, возможно, содержащих инструкцию #else (#elif использовать нельзя) и заканчивающихся строкой #endif. Если проверяемое условие истинно, то игнорируются все строки между #else и #endif, а если ложно, то строки между проверкой и #else (если слова #else нет, то #endif). Директивы #if и #ifndef могут "вкладываться" одна в другую.
Директива вида
#undef идентификатор
приводит к тому, что указанный идентификатор начинает считаться неопределенным, т.е. не подлежащим замене.
Рассмотрим примеры. Три следующие директивы:
#ifdef WRITE
#undef WRITE
#endif
проверяют определен ли идентификатор WRITE (т.е. была ли команда вида #define WRITE...), и если это так, то имя WRITE начинает считаться неопределенным, т.е. не подлежащим замене.
Директивы
#ifndef WRITE
#define WRITE fprintf
#endif
проверяют является ли идентификатор WRITE неопределенным, и если это так, то определятся идентификатор WRITE вместо имени fprintf.
Директива #error записывается в следующей форме:
#error сообщение_об_ошибке
Если она встречается в тексте программы, то компиляция прекращается и на экран дисплея выводится сообщение об ошибке. Эта команда в основном применяется на этапе отладки. Заметим, что сообщение об ошибке не надо заключать в двойные кавычки.
Директива #line предназначена для изменения значений переменных _LINE_ и _FILE_, определенных в системе программирования Си. Переменная _LINE_ содержит номер строки программы, выполняемой в текущий момент времени. Идентификатор _FILE_ является указателем на строку с именем компилируемой программы. Директива #line записывается следующим образом:
#line номер "имя_файла"
|
|
Здесь номер - это любое положительное целое число, которое будет назначено переменной _LINE_, имя_файла - это необязательный параметр, который переопределяет значение _FILE_.
Директива #pragma позволяет передать компилятору некоторые указания. Например, строка
#pragma inline
говорит о том, что в программе на языке Си имеются строки на языке ассемблера. Например:
asm mov ax, 5
asm {
inc dx
sub bl, al
}
и т.д.
Рассмотрим некоторые глобальные идентификаторы или макроимена (имена макроопределений). Определены пять таких имен: _LINE_, _FILE_, _DATE_, _TIME_, _STDC_. Два из них (_LINE_ и _FILE_) уже описывались выше. Идентификатор _DATE_ определяет строку, в которой сохраняется дата трансляции исходного файла в объектный код. Идентификатор _TIME_ задает строку, сохраняющую время трансляции исходного файла в объектный код. Макрос _STDC_ имеет значение 1, если используются стандартно - определенные макроимена. В противном случае эта переменная не будет определена.