Курс на Си. Подбельский. Курс программирования на Си. В., Фомин С. С. Курс программирования на языке Си Учебник
Скачать 1.57 Mb.
|
#undef идентификатор После выполнения такой директивы идентификатор для препроцессора становится неопределенным, и его можно определять повторно. Например: #define M 16 #undef M #define M 'C' #undef M #define M "C" Директиву #undef удобно использовать при разработке больших программ, когда они собираются из отдельных «кусков текста», написанных в разное время или разными программистами. В этом случае могут встретиться одинаковые обозначения разных объектов. Чтобы не изменять исходных файлов, включаемый текст можно «обрамлять» подходящими директивами #define, #undef и тем самым устранять возможные ошибки. Приведем пример:
При выполнении программы переменная B примет значение 10, несмотря на наличие оператора присваивания А = 5; во включенном тексте. Включение текстов из файлов Для включения текста из файла, как мы уже неоднократно показывали, используется команда #include, имеющая три формы записи: #include < имя_файла > /* Имя в угловых скобках */ #include «имя_файла» /* Имя в кавычках */ #include имя_макроса /* Макрос, расширяемый до обозначения файла*/ где имя_макроса - это введенный директивой #define препроцессор- ный идентификатор либо макрос, при замене которого после конечного числа подстановок будет получена последовательность символов <имя_файла> либо «имя_файла». (О макросах см. ниже, в §3.5.) До сих пор мы в программах и примерах фрагментов программ использовали только первую форму команды #include. Существует правило, что если имя_файла - в угловых скобках, то препроцессор разыскивает файл в стандартных системных каталогах. Если имя_файла заключено в кавычки, то вначале препроцессор просматривает текущий каталог пользователя и только затем обращается к просмотру стандартных системных каталогов. Начиная работать с языком Си, пользователь сразу же сталкивается с необходимостью использования в программах средств ввода- вывода. Для этого в начале текста программы помещают директиву: #include <stdio.h> Выполняя эту директиву, препроцессор включает в программу средства связи с библиотекой ввода-вывода. Поиск файла stdio.h ведется в стандартных системных каталогах. По принятому соглашению суффикс .h приписывается тем файлам, которые содержат прототипы библиотечных функций, а также определения и описания типов и констант, используемых при работе с библиотеками компилятора. Эти файлы в литературе по языку Си принято называть заголовочными. Кроме такого в некоторой степени стандартного файла, каким является stdio.h, в заголовок программы могут быть включены любые другие файлы (стандартные или подготовленные специально). Перечень обозначений заголовочных файлов для работы с библиотеками компилятора утвержден стандартом языка. Ниже приведены названия этих файлов, а также краткие сведения о тех описаниях и определениях, которые в них включены. Большинство описаний - прототипы стандартных функций, а определены в основном константы (например, NULL, EOF), необходимые для работы с библиотечными функциями. Все имена, определенные в стандартных заголовочных файлах, являются зарезервированными именами: assert.h - диагностика программ; ctype.h - преобразование и проверка символов; errno.h - проверка ошибок; float.h - работа с вещественными данными; limits.h - предельные значения целочисленных данных; Препроцессорные средства locate.h - поддержка национальной среды; math.h - математические вычисления; setjump.h - возможности нелокальных переходов; signal.h - обработка исключительных ситуаций; stdarg.h - поддержка переменного числа параметров; stddef.h - дополнительные определения; stdio.h - средства ввода-вывода; stdlib.h - функции общего назначения (работа с памятью); string.h - работа со строками символов; time.h - определение дат и времени. В конкретных реализациях количество и наименования заголовочных файлов могут быть и другими. Стандартные заголовочные файлы могут быть нечаянно или нарочно включены в текст программы в любом порядке и по нескольку раз без отрицательных побочных эффектов. Однако действие включаемого заголовочного файла распространяется на текст программы только от места размещения директивы #include и до конца текстового файла (и всех включаемых в программу текстов). Заголовочные нестандартные файлы оказываются весьма эффективным средством при модульной разработке крупных программ, когда связь между модулями, размещаемыми в разных файлах, реализуется не только с помощью параметров, но и через внешние объекты, глобальные для нескольких или всех модулей. Описания таких внешних объектов (переменных, массивов, структур и т. п.) и прототипы функций помещаются в одном файле, который с помощью директив #include включается во все модули, где необходимы внешние объекты. В тот же файл можно включить и директиву подключения файла с описаниями библиотеки функций ввода-вывода. Заголовочный файл может быть, например, таким: #include /* Целые внешние переменные */ extern int ii, jj, ll; /* Вещественные внешние переменные */ extern float aa, bb; Если в программе используется несколько функций, то часто удобно текст каждой из них хранить в отдельном файле. При подготовке программы в виде одного модуля программист включает в нее тексты всех функций с помощью команд #include. Условная компиляция Директивы ветвлений. Условная компиляция обеспечивается в языке Си следующим набором директив, которые, не точно соответствуя названию, управляют не компиляцией, а препроцессорной обработкой текста: #if целочисленное_константное_выражение #ifdef идентификатор #ifndef идентификатор #else #endif #elif Первые три директивы выполняют проверку условий, две следующие - позволяют определить диапазон действия проверяемого условия. (Директиву #elif рассмотрим несколько позже.) Общая структура применения директив условной компиляции такова: #if ... текст_1 #else текст_2 #endif Конструкция #else текст_2 необязательна. Текст_1 включается в компилируемый текст только при истинности проверяемого условия (обозначено многоточием после #if). Если условие ложно, то при наличии директивы #else на компиляцию передается текст_2. Если директива #else и текст_2 отсутствуют, то весь текст от #if до #endif при ложном условии опускается. Различие между формами команд #if состоит в следующем. В первой из перечисленных директив #if целочисленное_константное_выражение проверяется значение константного выражения, в которое могут входить целые константы и идентификаторы. Идентификаторы могут быть определены на препроцессорном уровне, и тогда их значение определяется подстановками. В противном случае считается, что идентификаторы имеют нулевые значения. Если константное выражение отлично от нуля, то считается, что проверяемое условие истинно. Например, в результате выполнения директив: #if 5+4 текст_1 #endif текст_1 всегда будет включен в компилируемую программу. В директиве #ifdef идентификатор проверяется, определен ли с помощью директивы #define к текущему моменту идентификатор, помещенный после #ifdef. Если идентификатор определен, то есть является препроцессорным, то текст_1 используется компилятором. В директиве #ifndef идентификатор проверяется обратное условие - истинным считается неопределенность идентификатора, то есть тот случай, когда идентификатор не был использован в команде #define или его определение было отменено командой #undef. Условную компиляцию удобно применять при отладке программ для включения или исключения средств вывода контрольных сообщений. Например: #define DEBUG #ifdef DEBUG printf ( " Отладочная печать "); #endif Таких вызовов функции printf( ), появляющихся в программе в зависимости от определенности идентификатора DEBUG, может быть несколько, и, убрав либо поместив в скобки комментария /*...*/ директиву #define DEBUG, сразу же отключаем все отладочные средства. Файлы, предназначенные для препроцессорного включения в программу, обычно снабжают защитой от повторного включения. Такое повторное включение может произойти, если несколько файлов, в каждом из которых, в свою очередь, запланировано препроцес- сорное включение одного и того же файла, объединяются в общий текст программы. Например, подобными средствами защиты снабжены все заголовочные файлы стандартной библиотеки. Схема защиты от повторного включения может быть такой: /* Файл с именем filename */ /* Проверка определенности _FILE_NAME */ #ifndef _FILE_NAME .../* Включаемый текст файла filename */ /* Определение _FILE_NAME */ #define _FILE_NAME #endif Здесь _FILE_NAME - зарезервированный для файла filename препроцессорный идентификатор, который желательно не использовать в других текстах программы. Для организации мультиветвлений во время обработки препроцессором исходного текста программы введена директива #elif целочисленное_константное_выражение Требования к целочисленному_константному_выражению те же, что и в директиве #if. Структура исходного текста с применением этой директивы такова: #if условие текст_для_1/ #elif выражение_1 текст_1 #elif выражение_2 текст_2 ... #else текст_для_случая_вке #endif Препроцессор проверяет вначале условие в директиве #if. Если оно ложно (равно 0), вычисляется выражение_1, если при этом оказывается, что и значением выражения_1 также является 0, вычисляется выражение_2 и т. д. Если все выражения ложны (равны 0), то в компилируемый текст включается текст_для_случая_е18в. В противном случае, то есть при появлении хотя бы одного истинного выражения (в #if или в #elif), начинает обрабатываться текст, расположенный непосредственно за этой директивой, а все остальные директивы не рассматриваются. Таким образом, при использовании директив условной компиляции препроцессор обрабатывает всегда только один из участков текста. Операция defined. При условной обработке текста (при условной компиляции с использованием директив #if, #elif) для упрощения записи сложного условия выбора можно использовать унарную пре- процессорную операцию defined операнд где операнд - либо идентификатор, либо заключенный в скобки идентификатор, либо обращение к макросу (см. §3.5). Если идентификатор операнда до этого определен с помощью команды #define как препроцессорный, то выражение defined операнд принимает значение 1L, то есть считается истинным. В противном случае его значение равно 0L. Выражение #if defined операнд эквивалентно выражению #ifdef операнд Но в таком простом случае никакие достоинства операции defined не проявляются. Поясним с помощью примера другие полезные возможности операции defined. Предположим, что некоторый важный_ текст должен быть передан компилятору только в том случае, если идентификатор Y определен как препроцессорный, а идентификатор N не определен. Директивы препроцессора могут быть записаны следующим образом: #if defined Y && !defined N важный_текст #endif Обработку препроцессор ведет следующим образом. Во-первых, определяется истинность выражений defined Y и defined N. Получаем два значения, каждое 0L или 1L. К результатам применяется операция && (конъюнкция), и при истинности ее результата важ- ный_текст передается компилятору. Не используя операцию defined, то же самое условие можно записать таким способом: #ifdef Y #ifndef N важный_текст #endif #endif Таким образом, из примера видно, что: #if defined эквивалентно #ifdef #if !defined эквивалентно #ifndef Стандарт языка Си не включает defined в набор ключевых слов. В тексте программы его можно использовать в качестве идентификатора, свободно применяемого программистом для обозначения объектов. defined имеет специфическое значение только при формировании выражений-условий, проверяемых в директивах #if и #elif. Однако идентификатор defined запрещено использовать в директивах #define и #undef. Макроподстановки средствами препроцессора Макрос, по определению, есть средство замены одной последовательности символов на другую. Для выполнения замен должны быть заданы соответствующие макроопределения. Простейшее макроопределение мы уже ввели, рассматривая замены в тексте с помощью директивы #define идентификатор строка_замещения С помощью директивы #define программист может вводить собственные обозначения базовых или производных типов. Например, директива #define REAL long double вводит название (имя) REAL для типа long double. Далее в тексте программы можно определять конкретные объекты, используя REAL в качестве обозначения их типа (long double): REAL x, array [6]; Идентификатор в команде #define может определять, как мы видели, имя константы, если строка_замещения задает значение этой константы. В более общем случае идентификатор служит обозначением некоторого выражения, например: #define RANGE ((INT_MAX)-(INT_MIN)+1) Идентификаторы, входящие в строку замещения, в свою очередь, могут быть определены как препроцессорные, и их значения будут подставлены вместо них (вместо INT_MAX и INT_MIN в нашем примере). Допустимость выполнять с помощью #define «цепочки» подстановок расширяет возможности этой директивы, однако она имеет существенный недостаток - строка замещения фиксирована. Большие возможности предоставляет макроопределение с параметрами #define имя(список_параметров) строка_замещения Здесь имя - имя макроса (идентификатор), список_параметров - список разделенных запятыми идентификаторов. Между именем макроса и скобкой, открывающей список параметров, не должно быть пробелов. Для обращения к макросу («для вызова макроса») используется конструкция («макровызов») вида: имя_макроса (список_аргументов) В списке аргументы разделены запятыми. Каждый аргумент - препроцессорная лексема. Классический пример макроопределения #define max(a,b) (a < b ? b : a) позволяет формировать в программе выражение, определяющее максимальное из двух значений аргументов. При таком определении вхождение в программу макровызова max(X,Y) заменяется выражением (X а использование конструкции вида max(Z,4) приведет к формированию выражения (Z<4? 4:Z) В первом случае при истинном значении X < Y возвращается значение Y, иначе - значение X. Во втором примере значение переменной Z сравнивается с константой 4 и выбирается большее из значений. Не менее часто используется определение #define ABS(X) (X<0? -(X):X) С его помощью в программу можно вставлять выражение для определения абсолютных значений переменных. Конструкция ABS(E-Z) заменяется выражением (E-Z<0? -(E-Z):E-Z) в результате вычисления которого определяется абсолютное значение выражения E-Z. Обратите внимание на скобки. Без них могут появиться ошибки в результатах. Следует отметить, что последовательные препроцессорные подстановки выполняются в строке замещения, но не действуют на параметры макроса. Моделирование многомерных массивов. В качестве еще одной области эффективного использования макросов укажем на проблему представления многомерных массивов в языке Си. Массивы мы будем подробно рассматривать в следующих главах, а пока остановимся только на вопросе применения макросредств для удобной адресации элементов матрицы. Напомним общие принципы представления массивов в языке Си. Основным понятием в языке Си является одномерный массив, а возможности формирования многомерных массивов (особенно с переменными размерами) весьма ограничены. Нумерация элементов массивов в языке Си начинается с нуля, то есть для обращения к начальному (первому) элементу массива требуется нулевое значение индекса. При работе с матрицами обе указанные особенности массивов языка Си создают, по крайней мере, неудобства. Во-первых, при обращении к элементу матрицы нужно указывать два индекса - номер строки и номер столбца элемента матрицы. Во-вторых, нумерацию строк и столбцов матрицы принято начинать с 1. Применение макросов для организации доступа к элементам массива позволяет программисту обойти оба указанных затруднения, правда, за счет нетрадиционных обозначений индексированных элементов. (Индексы в макросах, представляющих элементы массивов матриц, заключены в круглые, а не в квадратные скобки.) Рассмотрим следующую программу: define N 4 /* Число строк матрицы */ define M 5 /* Число столбцов матрицы */ define A(i,j) x[M*(i-1) + (j-1)] #include /* Определение одномерного массива */ double x[N*M]; int i,j, k; for (k=0; k < N*M; k++) x[k]=k; for (i=1; i<=N; i++) /* Перебор строк */ { printf ("\n Строка %d:", i); /* Перебор элементов строки */ for (j=1; j<=M; j++) printf(" %6.1f", A(i, j)); } } Результат выполнения программы:
В программе определен одномерный массив x[ ], количество элементов в котором зависит от значений препроцессорных идентификаторов N и M. Значения элементам массива x[ ] присваиваются в цикле с параметром k. Никаких новинок здесь нет. А вот далее для доступа к элементам того же массива x[ ] используются макровызовы вида A(i, j), причем i изменяется от 1 до N, а переменная j изменяется во внутреннем цикле от 1 до M. Переменная i соответствует номеру строки матрицы, а переменная j играет роль второго индекса, то есть указывает номер столбца. При таком подходе программист оперирует с достаточно естественными обозначениями A(i, j) элементов матрицы, причем нумерация столбцов и строк начинается с 1, как и предполагается в матричном исчислении. В тексте программы за счет макрорасширений в процессе препро- цессорной обработки выполняются замены параметризованных обозначений A(i, j) на x[5*(i-1)+(j-1)], и далее действия выполняются над элементами одномерного массива x[ ]. Но этих преобразований программист не видит и может считать, что он работает с традиционными обозначениями матричных элементов. Использованный в программе оператор (вызов функции) printf ("% 6.1f", A (i, j)); после макроподстановок будет иметь вид: printf ("«% 6.1f", x[5*(i-1)+(j-1)]); На рис. 3.1 приведена иллюстративная схема одномерного массива x[ ] и виртуальной (существующей только в воображении программиста, использующего макроопределения) матрицы для рассмотренной программы. Матрица А с размерами 4x5 Рис. 3.1. Имитация матрицы с помощью макроопределения и одномерного массива: A(1,1) соответствуетx[5*(1-1)+(1-1)] = = x[0]; A(1,2) соответствуетx[5*(1-1)+(2-1)] = = x[1]; A (2,1) соответствует x[5*(2-1)+(1-1)] = = x[5]; A (3,4) соответствует x[5*(3-1)+(4-1)] = = x[13] Отличия макросов от функций. Сравнивая макросы с функциями, заметим, что, в отличие от функции, определение которой всегда присутствует в одном экземпляре, тексты, формируемые макросом, вставляются в программу столько раз, сколько раз используется макрос. Обратим внимание на еще одно отличие: функция определена для данных того типа, который указан в спецификации ее параметров и возвращает значение только одного конкретного типа. Макрос пригоден для обработки параметров любого типа, допустимых в выражениях, формируемых при обработке строки замещения. Тип получаемого значения зависит только от типов параметров и от самих выражений. Таким образом, макрос может заменять несколько функций. Например, приведенные макросы max( ) и ABS( ) верно работают для параметров любых целых и вещественных типов, а результат зависит только от типов параметров. Отметим как рекомендацию, что для устранения неоднозначных или неверных использований макроподстановок параметры в строке замещения и ее саму полезно заключать в скобки. Еще одно отличие: аргументы функций - это выражения, а аргументы вызова макроса - препроцессорные лексемы, разделенные запятыми. Аргументы макрорасширениям не подвергаются. Препроцессорные операции в строке замещения. В последовательности лексем, образующей строку замещения, предусматривается использование двух операций - '#' и '##', первая из которых помещается перед параметром, а вторая - между любыми двумя лексемами. Операция '#' требует, чтобы текст, замещающий данный параметр в формируемой строке, заключался в двойные кавычки. В качестве полезного примера с операцией '#' рассмотрим следующее макроопределение: #define print (A) printf (#A"=%f", A) К макросу print (A) можно обращаться, подставляя вместо параметра A произвольные выражения, формирующие результаты вещественного типа. Пример: print (sin (a/2)); - обращение к макросу; printf ("sin (a/2)""=%f", sin (a/2)); - макрорасширение. Фрагмент программы: double a=3.14159; print (sin (a/2)); Результат выполнения (на экране дисплея): sin (a/2)=1.0 Операция '##', допускаемая только между лексемами строки замещения, позволяет выполнять конкатенацию лексем, включаемых в строку замещения. Чтобы пояснить роль и место операции '##', рассмотрим, как будут выполняться макроподстановки в следующих трех макроопределениях с одинаковым списком параметров и одинаковыми аргументами. #define zero (a, b, c, d) a (bcd) #define one (a, b, c, d) a (b c d) #define two (a, b, c, d) a (b##c##d) Макровызов zero(sin, x, +, y) one(sin, x, +, y) two(sin, x, +, y) Результат макроподстановки sin(bcd) sin(x + y) sin(x+y) В случае zero( ) последовательность «bcd» воспринимается как отдельный идентификатор. Замена параметров b, c, d не выполнена. В строке замещения макроса one( ) аргументы отделены пробелами, которые сохраняются в результате. В строке замещения для макроса two( ) использована операция '##', что позволило выполнить конкатенацию аргументов без пробелов между ними. Вспомогательные директивы В отличие от директив #include, #define и всего набора команд условной компиляции (#if...), рассматриваемые в данном параграфе директивы не так часто используются в практике программирования. Препроцессорные обозначения строк. Для нумерации строк можно использовать директиву #line константа которая указывает компилятору, что следующая ниже строка текста имеет номер, определяемый целой десятичной константой. Директива может одновременно изменять не только номер строки, но и имя файла: #line константа «имя_файла» Как пишут в литературе по языку Си [5], директиву #line можно «встретить» сравнительно редко, за исключением случая, когда текст программы на языке Си генерирует какой-то другой препроцессор. Смысл директивы #line становится очевидным, если рассмотреть текст, который препроцессор формирует и передает на компиляцию. После препроцессорной обработки каждая строка имеет следующий вид: имя_файла номер_строки текст_на_языке_Си Например, пусть препроцессор получает для обработки файл «www.c» с таким текстом: define N 3 /* Определение константы */ void main ( ) { #line 23 "file.c" double z[3*N]; } После препроцессора в файле с именем «www.i» будет получен следующий набор строк: www.c 11: www.c 12: void main( ) www.c 13: { www.c 14: file.c 23: double z[3*3] file.c 24: } Обратите внимание на отсутствие в результирующем тексте пре- процессорных директив и комментария. Соответствующие строки пусты, но включены в результирующий текст. Для них выделены порядковые номера (1 и 4). Следующая строка за директивой #line обозначена в соответствии со значением константы (23) и указанным именем файла «file.c». Реакция на ошибки. Обработка директивы #error последовательность_лексем приводит к выдаче диагностического сообщения в виде, определенном последовательностью лексем. Естественно применение директивы #error совместно с условными препроцессорными командами. Например, определив некоторую препроцессорную переменную NAME #define NAME 5 в дальнейшем можно проверить ее значение и выдать сообщение, если у NAME окажется другое значение: if (NAME != 5) error NAME должно быть равно 5 ! Сообщение будет выглядеть так: Error <имя_файла> <номер_строки>: Error directive: NAME должно быть равно 5 ! В случае выявления такой аварийной ситуации дальнейшая пре- процессорная обработка исходного текста прекращается, и только та часть текста, которая предшествует условию #if..., попадает в выходной файл препроцессора. Пустая директива. Существует директива, использование которой не вызывает никаких действий. Она имеет вид: # Прагмы. Директива #pragma последовательность_лексем определяет действия, зависящие от конкретной реализации компилятора. Например, в некоторые компиляторы входит вариант этой директивы для извещения компилятора о наличии в тексте программы команд на языке ассемблера. Возможности команды #pragma могут быть весьма разнообразными и важными. Стандарта для них не существует. Если конкретный препроцессор встречает прагму, которая ему неизвестна, он ее просто игнорирует как пустую директиву. В некоторых реализациях включена прагма #pragma pack(n) где n может быть 1, 2 или 4. Прагма «pack» позволяет влиять на упаковку смежных элементов в структурах и объединениях (см. главу 6). Соглашение может быть таким: pack(1) - выравнивание элементов по границам байтов; pack(2) - выравнивание элементов по границам слов; pack(4) - выравнивание элементов по границам двойных слов. В некоторые компиляторы включены прагмы, позволяющие изменять способ передачи параметров функциям, порядок помещения параметров в стек и т. д. Встроенные макроимена Существуют встроенные (заранее определенные) макроимена, доступные препроцессору во время обработки. Они позволяют получить следующую информацию: _ _LINE_ _ - десятичная константа - номер текущей обрабатываемой строки файла с программой на Си. Принято, что номер первой строки исходного файла равен 1; _ _FILE_ _ - строка символов - имя компилируемого файла. Имя изменяется всякий раз, когда препроцессор встречает директиву #include с указанием имени другого файла. Когда включения файла по команде #include завершаются, восстанавливается предыдущее значение макроимени _ _FILE_ _; _ _DATE_ _ - строка символов в формате «месяц число год», определяющая дату обработки исходного файла. Например, после препроцессорной обработки текста программы, выполненной 10 марта 2011 года, оператор printf(_ _DATE_ _); станет таким: printf("Mar 10 2011"); _ _TIME_ _ - строка символов вида «часы:минуты:секунды», определяющая время начала обработки препроцессором исходного файла; _ _STDC_ _ - константа, равная 1, если компилятор работает в соответствии с ANSI-стандартом. В противном случае значение макроимени _ _STDC_ _ не определено. Стандарт языка Си предполагает, что наличие имени _ _STDC_ _ определяется реализацией, так как макрос _ _STDC_ _ относится к нововведениям стандарта. В конкретных реализациях набор предопределенных имен зачастую шире. Для получения более полных сведений о предопределенных пре- процессорных именах следует обращаться к документации по конкретному компилятору. Контрольные вопросы Укажите формы препроцессорной директивы #include. Объясните возможности использования директивы #include во включаемых текстах. Для чего используется препроцессорная директива #define? Объясните назначение директивы #undef. Как препроцессорными средствами защитить текст от повторных включений? С какого символа начинается препроцессорная директива? Может ли препроцессор обрабатывать тексты, отличные от кода на языке Си? Что является результатом работы препроцессора? Как препроцессор обрабатывает строку, начинающуюся с символа # с последующим пробелом? Объясните область действия директивы #define. Объясните формат объявления препроцессорного идентификатора. Можно ли препроцессорный идентификатор последовательно связывать с разными строками замещений? Как с помощью директивы #define ввести константу? Перечислите препроцессорные операции. Как с помощью директив препроцессора разметить текст, чтобы он был «чувствителен» к количеству его включений в программу? Как с помощью директивы #define можно вести макроопределение? Где используется препроцессорная операция #? Что нужно, чтобы включить в строку замещения макроса несколько операторов языка Си? Как разместить строку замещения директивы #define в нескольких строках текста программы? Перечислите различия и сходства макросов и функций. Назовите препроцессорные директивы ветвлений обработки текста. Что может служить операндом логического препроцессорного выражения? Укажите назначения и возможности препроцессорных операций. Чем отличаются препроцессорные макроопределения от макроподстановок? Как организовать рекурсивную обработку текста с помощью средств препроцессора? Как сделать идентификатор определенным для препроцессора? Может ли содержать препроцессорные директивы текст, включаемый в программу директивой #include? Для чего в строке замещения макроса может быть использована препроцессорная операция ##? Почему имена параметров макроса в строке замещения рекомендуется заключать в круглые скобки? Приведите примеры использования директивы #pragma. |