Главная страница

Курс на Си. Подбельский. Курс программирования на Си. В., Фомин С. С. Курс программирования на языке Си Учебник


Скачать 1.57 Mb.
НазваниеВ., Фомин С. С. Курс программирования на языке Си Учебник
АнкорКурс на Си
Дата18.02.2023
Размер1.57 Mb.
Формат файлаdocx
Имя файлаПодбельский. Курс программирования на Си.docx
ТипУчебник
#943863
страница12 из 42
1   ...   8   9   10   11   12   13   14   15   ...   42

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

После выполнения такой директивы идентификатор для препро­цессора становится неопределенным, и его можно определять по­вторно. Например:

#define M 16

#undef M

#define M 'C'

#undef M

#define M "C"

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

A =

10;

/

Основной текст */

...

#define

A X







...

A =

5;

/

Включенный текст */

...

#undef

A







...

B =

A;

/

Основной текст */

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

    1. Включение текстов из файлов

Для включения текста из файла, как мы уже неоднократно пока­зывали, используется команда #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.

    1. Условная компиляция

Директивы ветвлений. Условная компиляция обеспечивается в языке Си следующим набором директив, которые, не точно соот­ветствуя названию, управляют не компиляцией, а препроцессорной обработкой текста:

#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.

    1. Макроподстановки средствами

препроцессора

Макрос, по определению, есть средство замены одной последо­вательности символов на другую. Для выполнения замен должны быть заданы соответствующие макроопределения. Простейшее мак­роопределение мы уже ввели, рассматривая замены в тексте с по­мощью директивы

#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. Основным понятием в языке Си является одномерный массив, а возможности формирования многомерных массивов (особен­но с переменными размерами) весьма ограничены.

  2. Нумерация элементов массивов в языке Си начинается с ну­ля, то есть для обращения к начальному (первому) элементу массива требуется нулевое значение индекса.

При работе с матрицами обе указанные особенности массивов языка Си создают, по крайней мере, неудобства. Во-первых, при об­ращении к элементу матрицы нужно указывать два индекса - номер строки и номер столбца элемента матрицы. Во-вторых, нумерацию строк и столбцов матрицы принято начинать с 1.

Применение макросов для организации доступа к элементам мас­сива позволяет программисту обойти оба указанных затруднения, правда, за счет нетрадиционных обозначений индексированных эле­ментов. (Индексы в макросах, представляющих элементы массивов матриц, заключены в круглые, а не в квадратные скобки.) Рассмот­рим следующую программу:

  • define N 4 /* Число строк матрицы */

  • define M 5 /* Число столбцов матрицы */

  • define A(i,j) x[M*(i-1) + (j-1)]

#include void main ( ) {

/* Определение одномерного массива */ 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));

}

}

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

Строка

1:

10.0

11.0

12.0

13.0

14.0

Строка

2:

15.0

16.0

17.0

18.0

19.0

Строка

3:

10.0

11.0

12.0

13.0

14.0

Строка

4:

15.0

16.0

17.0

18.0

19.0

В программе определен одномерный массив 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( ) использована операция '##', что позволило выполнить кон­катенацию аргументов без пробелов между ними.

    1. Вспомогательные директивы

В отличие от директив #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) - выравнивание элементов по границам двойных слов.

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

    1. Встроенные макроимена

Существуют встроенные (заранее определенные) макроимена, до­ступные препроцессору во время обработки. Они позволяют полу­чить следующую информацию:

_ _LINE_ _ - десятичная константа - номер текущей обрабаты­ваемой строки файла с программой на Си. Принято, что номер первой строки исходного файла равен 1;

_ _FILE_ _ - строка символов - имя компилируемого файла. Имя изменяется всякий раз, когда препроцессор встречает директиву #include с указанием имени другого файла. Когда включения файла по команде #include завершаются, восстанавливается предыдущее значение макроимени _ _FILE_ _;

_ _DATE_ _ - строка символов в формате «месяц число год», определяющая дату обработки исходного файла. Например, после препроцессорной обработки текста программы, выполненной 10 марта 2011 года, оператор

printf(_ _DATE_ _);

станет таким:

printf("Mar 10 2011");

_ _TIME_ _ - строка символов вида «часы:минуты:секунды», определяющая время начала обработки препроцессором исходного файла;

_ _STDC_ _ - константа, равная 1, если компилятор работает в соответствии с ANSI-стандартом. В противном случае значение макроимени _ _STDC_ _ не определено. Стандарт языка Си предполагает, что наличие имени _ _STDC_ _ определяется реализацией, так как макрос _ _STDC_ _ относится к нововведениям стандарта.

В конкретных реализациях набор предопределенных имен зачас­тую шире.

Для получения более полных сведений о предопределенных пре- процессорных именах следует обращаться к документации по кон­кретному компилятору.

Контрольные вопросы

  1. Укажите формы препроцессорной директивы #include.

  2. Объясните возможности использования директивы #include во включаемых текстах.

  3. Для чего используется препроцессорная директива #define?

  4. Объясните назначение директивы #undef.

  5. Как препроцессорными средствами защитить текст от повтор­ных включений?

  6. С какого символа начинается препроцессорная директива?

  7. Может ли препроцессор обрабатывать тексты, отличные от кода на языке Си?

  8. Что является результатом работы препроцессора?

  9. Как препроцессор обрабатывает строку, начинающуюся с симво­ла # с последующим пробелом?

  10. Объясните область действия директивы #define.

  11. Объясните формат объявления препроцессорного идентифика­тора.

  12. Можно ли препроцессорный идентификатор последовательно связывать с разными строками замещений?

  13. Как с помощью директивы #define ввести константу?

  14. Перечислите препроцессорные операции.

  15. Как с помощью директив препроцессора разметить текст, чтобы он был «чувствителен» к количеству его включений в программу?

  16. Как с помощью директивы #define можно вести макроопреде­ление?

  17. Где используется препроцессорная операция #?

  18. Что нужно, чтобы включить в строку замещения макроса не­сколько операторов языка Си?

  19. Как разместить строку замещения директивы #define в несколь­ких строках текста программы?

  20. Перечислите различия и сходства макросов и функций.

  21. Назовите препроцессорные директивы ветвлений обработки текста.

  22. Что может служить операндом логического препроцессорного выражения?

  23. Укажите назначения и возможности препроцессорных операций.

  24. Чем отличаются препроцессорные макроопределения от макро­подстановок?

  25. Как организовать рекурсивную обработку текста с помощью средств препроцессора?

  26. Как сделать идентификатор определенным для препроцессора?

  27. Может ли содержать препроцессорные директивы текст, вклю­чаемый в программу директивой #include?

  28. Для чего в строке замещения макроса может быть использована препроцессорная операция ##?

  29. Почему имена параметров макроса в строке замещения рекомен­дуется заключать в круглые скобки?

  30. Приведите примеры использования директивы #pragma.

1   ...   8   9   10   11   12   13   14   15   ...   42


написать администратору сайта