logo search
Подбельский Фомин_Программирование на языке СИ_

Цепочка подстановок.

Цепочка подстановок. Если в строке_замещения команды #define в качестве отдельной лексемы встречается препроцессорный идентификатор, ранее определенный другой директивой #define, то выполняется цепочка последовательных подстановок. В качестве примера рассмотрим, как можно определить диапазон (RANGE) возможных значений любой целой переменной типа int в следующей программе:

Препроцессор последовательно, строку за строкой, просматривает текст и, обнаружив директиву #include <limits.h>, вставляет текст из файла limits.h. Там определены константы INT_MAX (предельное максимальное значение целых величин), INT_MIN (предельное минимальное значение целых величин). Тем самым программа принимает, например, такой вид:

Обратите внимание, что директива #include исчезла из программы, но ее заменил соответствующий текст.

Обнаружив в тексте (добытом из файла limits.h) директивы #define..., препроцессор выполняет соответствующие подстановки, и программа принимает вид:

Подстановки изменили строку замещения препроцессорного идентификатора RANGE в директиве #define, размещенной ниже, чем текст, включенный из файла limits.h "Продвигаясь" по тексту программы, препроцессор встречает препроцессорный идентификатор RANGE и выполняет подстановку. Текст программы приобретает следующий вид:

Теперь все директивы #define удалены из текста. Получен текст, пригодный для компиляции, т.е. создана "единица трансляции". Подстановка строки замещения вместо идентификатора RANGE выполнена в выражении RANGE/8, однако внутри комментария идентификатор RANGE остался без изменений и не изменился идентификатор RANGE_T.

Этот пример иллюстрирует выполнение "цепочки" подстановок и ограничения на замены: замены не выполняются внутри комментариев, внутри строковых констант, внутри символьных констант и внутри идентификаторов (не может измениться часть идентификатора). Например, RANGE_T остался без изменений. Для еще одной иллюстрации перечисленных ограничений рассмотрим такой фрагмент программы:

В ходе препроцессорной обработки этого текста замена n на 24 будет выполнена только один раз в последнем определении, которое примет вид:

Все остальные вхождения символа n в текст программы препроцессор просто "не заметит".

Вернемся к формату директивы #define.

Если строка_замещения оказывается слишком длинной, то, как уже говорилось, ее можно продолжить в следующей строке текста. Для этого в конце продолжаемой строки помещается символ '\'. В ходе одной из стадий препроцессорной обработки этот символ вместе с последующим символом конца строки будет удален из текста программы. Пример:

С помощью команды #define удобно выполнять настройку программы. Например, если в программе требуется работать с массивами, то их размеры можно явно определять на этапе препроцессорной обработки:

При таком описании очень легко изменять предельные размеры сразу всех массивов, изменив только одну константу (строку замещения) в директиве #define.

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

Если в программе нужно часто печатать или выводить на экран дисплея значение какой-либо переменной и, кроме того, снабжать эту печать одним и тем же пояснительным текстом, то удобно ввести сокращенное обозначение оператора печати, например:

После этой директивы использование в программе оператора РК; будет эквивалентно (по результату) оператору из строки замещения. Например, последовательность операторов

приведет к выводу такого текста:

Если в строку замещения входит идентификатор, определенный в другой команде #define, то в строке замещения выполняется следующая замена (цепочка подстановок). Например, программа, содержащая команды:

выведет на экран такой текст:

Обратите внимание, что идентификатор K внутри строки замещения, обрамленной кавычками ("), не заменен на 50.

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

Однако при таких сменах значений препроцессорного идентификатора компилятор Borland C++ выдает предупреждающее сообщение на каждую следующую директиву #define:

Замены в тексте можно отменять с помощью команды

#undef идентификатор

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

Директиву #undef удобно использовать при разработке больших программ, когда они собираются из отдельных "кусков текста", написанных в разное время или разными программистами. В этом случае могут встретиться одинаковые обозначения разных объектов. Чтобы не изменять исходных файлов, включаемый текст можно "обрамлять" подходящими директивами #define, #undef и тем самым устранять возможные ошибки. Приведем пример:

При выполнении программы переменная В примет значение 10, несмотря на наличие оператора присваивания А = 5; во включенном тексте.