На этом шаге мы рассмотрим, какими дополнительными возможностями и директивами
обладает препроцессор.
Напомним основные возможности макроопределений, рассмотрев следующий пример.
#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(х)); (++х < ++х ? ++х : ++х) ,
PRNT(t(х+x));
значение выражения х + х*3. Чтобы подстановка t(e) утраивала значение любого аргумента, следует использовать скобки вокруг параметра в строке замещения, т.е. записать
#define t(e) (e)*3
PRNT(t(х+x)); PRNT(t(х+x)/3);
Приведенные примеры показывают, что для устранения неоднозначных или неверных использовании макроподстановок параметры в строке замещения и ее саму, по крайней мере, полезно заключать в скобки.
В заключение приведем еще некоторые сведения о работе препроцессора и его директивах.
При препроцессорной обработке исходного текста программы каждая строка обрабатывается отдельно. Напоминаем, что возможно "соединение" строк: если в конце строки стоит символ '\', а за ним - символ перехода на новую строку '\n', то эта пара символов исключается и следующая строка непосредственно присоединяется к текущей строке. Анализируя полученные строки, препроцессор распознает лексемы. Лексемами для препроцессора являются:
В последовательности лексем, образующей строку замещения, предусматривается использование двух операций - '#' и '##', первая из которых помещается перед параметром, а вторая - между любыми двумя лексемами. Операция '#', уже использованная выше в программе, требует, чтобы текст, замещающий данный параметр в формируемой строке, заключался в двойные кавычки. Например, для определения
#define sm(zip) cout << #zip
cout << "сумма"
Операция '##', допускаемая только между лексемами строки замещения, позволяет выполнять конкатенацию лексем, включаемых в строку замещения. Определение
#define abc(a,b,c,d) a##(##b##c##d)
Препроцессорная операция defined позволяет по-другому записать директивы условной компиляции. Конструкция:
#if defined
#if !defined
Для нумерации строк можно использовать директиву:
#line <константа>
которая указывает компилятору, что следующая ниже строка текста имеет номер, определяемый целой десятичной константой. Команда может определять не только номер строки, но и имя файла:
#line <константа> "<имя_файла>"
#error <последовательность_лексем>
приводит к выдаче диагностического сообщения в виде последовательности лексем. Естественно применение директивы #еrror совместно с условными препроцессорными командами. Например, определив некоторую препроцессорную переменную NAME
#define NAME 5
#if (NAME != 5) #error NAME должно быть равно 5!
fatal: <имя_файла> <номер_строки> #error directive: NAME должно быть равно 5!
#
#pragma <последовательность_лексем>
определяет действия, зависящие от конкретной реализации компилятора. Например, в
TC++ и BC++ входит вариант этой директивы для извещения компилятора о
наличии в тексте программы команд ассемблера.
На следующем шаге мы приведем перечень встроенных макроимен.