Шаг 80.
Дополнительные директивы препроцессора

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

    Напомним основные возможности макроопределений, рассмотрев следующий пример.

#include <iostream.h>
#define max(a,b) (a < b ? b:a)
#define t(e) e*3
#define PRNT(c) cout << "\n" << #c << " равно "; cout << c 
#define E x*x
void main()
{
  int x;
  x = 2;
  PRNT(max(++x, ++x));
  PRNT(t(x));
  PRNT(t(x+x));
  PRNT(t(x+x)/3);
  PRNT(E);
}
Текст этой программы можно взять здесь.

    Результат выполнения программы:

     max(++x, ++x) равно 5
     t(x) равно 15
     t(x+x) равно 20
     t(x+x)/3 равно 10
     E равно 25

    Обратите внимание на то, что в макроопределении PRNT строка замещения включает два оператора, разделенных точкой с запятой, причем в строке замещения имеются пробелы. При подстановке параметров макроса в строку замещения запрещены подстановки внутрь кавычек, апострофов или ограничителей комментариев. В случае необходимости параметр макроса можно заключить в строке замещения в кавычки (""). Для этого используется специальная операция #, которая записывается непосредственно перед параметром. В макроопределении PRNT(E) будет сформирована последовательность операторов

     cout << "\n" << "E" << " равно "; cout << E; 

    Последующая подстановка приведет к следующему результату:

     cout << "\n" << "E" << " равно "; cout << x*x; 

    Именно поэтому в результатах слева от слова "равно" печатается изображение параметра, использованного в PRNT.

    Рассмотрим результаты подробнее. В операторе

     PRNT(max(++х, ++х)); 
печатается значение выражения
     (++х < ++х ? ++х : ++х) ,

после вычисления которого х увеличивается на 3, т.е. становится равным 5. В операторе

     PRNT(t(х)); 
     (++х < ++х ? ++х : ++х) ,
печатается значение выражения x*3. В операторе
     PRNT(t(х+x)); 

значение выражения х + х*3. Чтобы подстановка t(e) утраивала значение любого аргумента, следует использовать скобки вокруг параметра в строке замещения, т.е. записать

     #define t(e) (e)*3 
При таком определении t(e) в операторах
     PRNT(t(х+x)); 
     PRNT(t(х+x)/3); 
сформировались бы выражения (х + х) *3, равное 30 и (х + х)*3/3, равное 10.

    Приведенные примеры показывают, что для устранения неоднозначных или неверных использовании макроподстановок параметры в строке замещения и ее саму, по крайней мере, полезно заключать в скобки.

    В заключение приведем еще некоторые сведения о работе препроцессора и его директивах.

    При препроцессорной обработке исходного текста программы каждая строка обрабатывается отдельно. Напоминаем, что возможно "соединение" строк: если в конце строки стоит символ '\', а за ним - символ перехода на новую строку '\n', то эта пара символов исключается и следующая строка непосредственно присоединяется к текущей строке. Анализируя полученные строки, препроцессор распознает лексемы. Лексемами для препроцессора являются:

Аргументы вызова макроса - лексемы, разделенные запятыми. Аргументы макрорасширениям не подвергаются.

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

     #define sm(zip) cout << #zip 
обращение (макровызов) sm(сумма); приведет к формированию оператора
     cout << "сумма" 

    Операция '##', допускаемая только между лексемами строки замещения, позволяет выполнять конкатенацию лексем, включаемых в строку замещения. Определение

     #define abc(a,b,c,d) a##(##b##c##d) 
позволит сформировать выражение sin(x+y), если использовать макровызов abc(sin,x,+,y).

    Препроцессорная операция defined позволяет по-другому записать директивы условной компиляции. Конструкция:

     #if defined 
есть аналог #ifdef и конструкция:
     #if !defined 
есть аналог #ifndef.

    Для нумерации строк можно использовать директиву:

     #line <константа>

которая указывает компилятору, что следующая ниже строка текста имеет номер, определяемый целой десятичной константой. Команда может определять не только номер строки, но и имя файла:

     #line <константа> "<имя_файла>"
Директива
     #error <последовательность_лексем>

приводит к выдаче диагностического сообщения в виде последовательности лексем. Естественно применение директивы #еrror совместно с условными препроцессорными командами. Например, определив некоторую препроцессорную переменную NAME

     #define NAME 5
в дальнейшем можно проверить ее значение и выдать сообщение, если у NAME другое значение:
     #if (NAME != 5)
     #error NAME должно быть равно 5!
Сообщение будет выглядеть так:
     fatal: <имя_файла> <номер_строки>
     #error directive: NAME должно быть равно 5!
Допустима пустая директива:
     #
не вызывающая никаких действий. Команда
     #pragma <последовательность_лексем>

определяет действия, зависящие от конкретной реализации компилятора. Например, в TC++ и BC++ входит вариант этой директивы для извещения компилятора о наличии в тексте программы команд ассемблера.

    На следующем шаге мы приведем перечень встроенных макроимен.


Предыдущий шаг Содержание Следующий шаг