Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

книги / Программирование на языке Си

..pdf
Скачиваний:
15
Добавлен:
12.11.2023
Размер:
17.16 Mб
Скачать

142

Программирование на языке Си

А = 5; / Включенный текст */

#undef А

В = А; / Основной текст */

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

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

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

#include < имя_файла > /* Имя в угловых скобках */

#include "имяфайла" /* Имя в кавычках */

#include имямакроса

/* Макрос, расширяемый до обозначения файла*/

где имя макроса - это введенный директивой #define препроцессорный идентификатор либо макрос, при замене которого после конечного числа подстановок будет получена последова­ тельность символов <имя_файла> либо "имя файла". (О макро­ сах см. ниже в §3.5.)

До сих пор мы в программах и примерах фрагментов про­ грамм использовали только первую форму команды #include. Существует правило, что если имя файла - в угловых скобках, то препроцессор разыскивает файл в стандартных системных каталогах. Если имя файла заключено в кавычки, то вначале препроцессор просматривает текущий каталог пользователя и только затем обращается к просмотру стандартных системных каталогов.

Глава 3. Препроцессорные средства

143

Начиная работать с языком Си, пользователь сразу же стал­ кивается с необходимостью использования в программах средств ввода-вывода. Для этого в начале текста программы по­ мещают директиву:

^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

Обработка исключительных ситуаций

144

 

Программирование на языке Си

stdarg.h

-

Поддержка переменного числа параметров

- stddef.h

-

Дополнительные определения

stdio.h

-

Средства ввода-вывода

stdlib.h

-

Функции общего назначения

 

 

(работа с памятью)

string.h

-

Работа со строками символов

time.h

- Определение дат и времени

В конкретных реализациях количество и наименования заго­ ловочных файлов могут быть и другими. Например, в компиля­ торах для MS-DOS активно используются заголовочные файлы с названиями mem.h, alloc.h, conio.h, dos.h и др. Кроме того, почти в каждой операционной системе имеется библиотека гра­ фических функций, для связи с которыми вводится соответст­ вующий заголовочный файл. Например, в компиляторах Turbo С и Borland C++ для связи с графической библиотекой применя­ ется заголовочный файл с названием graphics.h.

Стандартные заголовочные файлы могут быть нечаянно или нарочно включены в текст программы в любом порядке и по несколько раз без отрицательных побочных эффектов. Однако действие включаемого заголовочного файла распространяется на текст программы только в пределах одного модуля от места размещения директивы ^include и до конца текстового файла (и всех включаемых в программу текстов).

Заголовочные нестандартные файлы оказываются весьма эффективным средством при модульной разработке крупных программ, когда связь между модулями, размещаемыми в раз­ ных файлах, реализуется не только с помощью параметров, но и через внешние объекты, глобальные для нескольких или всех модулей. Описания таких внешних объектов (переменных, мас­ сивов, структур и т.п.) и прототипы функций помещаются в од­ ном файле, который с помощью директив ^include включается во все модули, где необходимы внешние объекты. В тот же файл можно включить и директиву подключения файла с опи­ саниями библиотеки функций ввода-вывода. Заголовочный файл может быть, например, таким:

Глава 3. Препроцессорные средства

145

#±ncTude <stdio.h> /* Включение средств обмена */ /* Целые внешние переменные */

extern int ii, jj, 11;

/* Вещественные внешние переменные */ extern float аа, bb;

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

В главе 8 приведены примеры разработки таких программ. Одним из примером служит задача формирования гистограммы с целью оценки последовательности псевдослучайных чисел. При разработке программы выделены шесть функций и введены внешние переменные, которые должны быть доступны для этих функций. Список не главных функций (их прототипы) получил­ ся (см. гл. 8) таким:

void count(void);

void estimate (double *, double *, double *); void compare (double, double);

double gauss (double, double); int pseudorand (void);

P'

Втексты всех перечисленных функций, а также в функцию

m ain() помещены прототипы вызываемых функций. Кроме того, в каждой функции описаны со спецификатором extern все внешние объекты, доступ к которым нужен в теле функции (о внешних объектах, спецификаторе extern и классах памяти под­ робно будет говориться в §5.7).

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

#include "main.с" #include "count.с"

10**3124

146

Программирование на языке Си

#± n c lu d e " e s t im a t e . с ” ♦ in c lu d e "c o m p a re . с "

#in c lu d e " g a u s s .с "

#in c lu d e "p s e u d o . c "

Имена файлов соответствуют именам функций с добавкой расширения ".с". Единственное исключение - имя файла "pseudo.c". Здесь имя функции pseudorand() укорочено, чтобы программа могла выполняться и для реализации под MS-DOS, где имена файлов не должны превышать восьми символов.

Как иллюстрирует этот пример, исходный текст программы может быть настолько сокращен, что будет содержать только директивы препроцессора.

Препроцессор включает настоящие (реальные) тексты всех функций в программу и как единое целое (модуль) передает по­ лученный текст на компиляцию.

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

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

#if целочисленное_константное_выражение

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

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

#else

#endif

#elif

Первые три директивы выполняют проверку условий, две следующие - позволяют определить диапазон действия прове­ ряемого условия. (Директиву #elif рассмотрим несколько поз­ же.) Общая структура применения директив условной ком­ пиляции такова:

Глава 3. Препроцессорные средства

147

# if...

текст ]

#else

текст_2

#endif

Конструкция #else текст_2 необязательна. Текст ! вклю­ чается в компилируемый текст только при истинности прове­ ряемого условия (обозначено многоточием после #if). Если условие ложно, то при наличии директивы #else на компиляцию передается текст_2. Если директива #else и текст_2 отсутст­ вуют, то весь текст от #if до #endif при ложном условии опуска­ ется. Различие между формами команд #if состоит в следующем.

В первой из перечисленных директив

#if целочисленное константное выражение

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

#if 5+4

текст!

#endif

текст ! всегда будет включен в компилируемую программу. В директиве

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

проверяется, определен ли с помощью директивы ^define к те­ кущему моменту идентификатор, помещенный после #ifdef. Если идентификатор определен, т.е. является препроцессорным, то текст ! используется компилятором.

148

Программирование на языке Си

В директиве

ftifndef идентификатор

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

Глава 3. Препроцессорные средства

149

Здесь _FILE_NAME - зарезервированный для

файла filename

препроцессорный идентификатор, который желательно не ис­ пользовать в других текстах программы.

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

#elif целочисленное константное_выражение

Требования к целочисленному константному выражению

те же, что и в директиве #if.

Структура исходного текста с применением этой директивы такова:

#ifусловие текст_для_(f

#elif выражение_1 текст_1

#elif выражение_2 текст_2

#else

текст для случая_else

#endif

Препроцессор проверяет вначале условие в директиве #if. Ес­ ли оно ложно (равно 0), - вычисляется выражение !, если при этом оказывается, что и значением выражения ! также являет­ ся 0, - вычисляется выражение_2, и т.д. Если все выражения ложны (равны 0), то в компилируемый текст включается текст для случая еЬе. В противном случае, т.е. при появлении хотя бы одного истинного выражения (в #if или в #elif), начинает обрабатываться текст, расположенный непосредствен­ но за этой директивой, а все остальные директивы не рассмат­ риваются.

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

150

Программирование на языке Си

Операция 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, то же самое условие можно записать таким наглядным способом:

Глава 3. Препроцессорные средства

151

#ifdef Y #ifndefN

важныйтекст

#endif

#endif

Таким образом, из примера видно, что:

#if defined

эквивалентно

#ifdef

#if {defined

эквивалентно

#ifndef

Стандарт языка Си не определил defined в качестве ключе­ вого слова. В тексте программы его можно использовать в каче­ стве идентификатора, свободно применяемого программистом для обозначения объектов, defined имеет специфическое значе­ ние только при формировании выражений-условий, проверяе­ мых в директивах #if и #elif. В то же время идентификатор defined запрещено использовать в директивах #define и #undef.

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

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

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

^define идентификатор строка замещения

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

#d e f in e REAL lo n g d o u b le

вводит название (имя) REAL для типа long donble. Далее в тек­ сте программы можно определять конкретные объекты, исполь­ зуя REAL в качестве обозначения их типа (long double):

Соседние файлы в папке книги