Урок 22. Препроцессор. Директивы

   ⁄ 

 Обновлено 24 Фев 2017

  ⁄   

Препроцессор лучше всего рассматривать как отдельную программу, которая выполняется перед компиляцией. При запуске, препроцессор просматривает каждый файл кода сверху вниз, в поиске директив. Директивы — это специфические команды, которые начинаются с символа # и заканчиваются вместе с новой строки (НЕ точкой с запятой). Есть несколько типов директив, которые мы рассмотрим ниже.

Include

Вы уже видели директивы #include в действии. Когда вы #include файл, препроцессор копирует содержимое включаемого файла и вставляет в активный файл сразу же после строчки #include. Это очень полезно, когда у вас есть информация, которая должна быть в нескольких местах (например, как предварительные объявления).

Команда #include имеет две формы:

#include <filename> сообщает препроцессору искать файл в специальном месте, определенном в операционной системе, где находятся файлы заголовков библиотеки С++ Runtime. Чаще всего вы будете использовать эту форму при подключении заголовочных файлов, которые поставляются вместе с компилятором (например, те, которые являются частью стандартной библиотеки C++).

#include "filename" сообщает препроцессору искать файл в директории, в которой находится текущий файл. Если заголовка там не будет, препроцессор начнет проверять любые другие пути, которые вы определили как часть настроек вашего компилятора/IDE. Эта форма используется для подключения своих собственных заголовочных файлов.

Macro define

Директиву #define можно использовать для создания макросов. Макрос — это правило, которое определяет, как последовательность input (например, идентификатор) преобразуется в последовательность output (например, в какой-нибудь текст).

Есть два основных типа макросов: макросы-функции и макросы-объекты.

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

Макросы-объекты можно определить одним из двух способов:

#define identifier
#define identifier substitution_text

Верхнее определение не имеет никакого substitution text, в то время как нижнее имеет. Поскольку это объявления препроцессора (не простые стейтменты), то, обратите внимание, ни одна форма не заканчивается точкой с запятой.

Макросы-объекты с текстом-заменой

Когда препроцессор встречает подобные директивы, любое дальнейшее появление ‘идентификатора’ заменяется на ‘substitution_text’. Идентификатор традиционно печатается заглавными буквами с символами подчеркивания в качестве пробелов.

Рассмотрим следующий фрагмент кода:

Препроцессор преобразует это в следующее:

Что, при запуске, выведет My favorite number is: 9.

Мы обсудим этот случай (и почему вы не должны так делать) более подробно в следующих уроках.

Макросы-объекты без текста-замены

Макросы, как объекты, также могут быть определены без текста-замены.

Например:

Такие макросы работают, как и стоит ожидать: любое дальнейшее появление идентификатора удаляется и заменяется «ничем»!

Это может показаться довольно бесполезным, тем не менее, это не основное назначение подобных директив. В отличие от объектных макросов с текстом-заменой, макросы без текста-замены, как правило, считаются приемлемыми для использования.

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

Директивы препроцессора условной компиляции позволяют определить, при каких условиях что-то будет или не будет компилироваться. В этом уроке мы рассмотрим только три директивы условной компиляции: #ifdef, #ifndef и #endif.

Директива #ifdef позволяет препроцессору проверить, было ли значение ранее #define. Если да, то код между #ifdef и #endif скомпилируется. Если нет, то код игнорируется.

Рассмотрим следующий фрагмент:

Поскольку PRINT_JOE уже был #define, строчка cout << "Joe" << std::endl;  скомпилируется. Поскольку PRINT_BOB не был #define, строчка cout << "Bob" << std::endl; не скомпилируется.

#ifndef – полная противоположность к #ifdef и позволяет проверить, НЕ было ли значения ранее определено.

Эта программа выведет «Bob», так как PRINT_BOB ранее никогда не был #define.

Условная компиляция очень часто используется в качестве header guards. О них мы поговорим в следующем уроке.

А вот здесь вы можете удивиться (кусочек верхнего фрагмента):

Так как мы определили PRINT_JOE как «ничего» (пустое место), как же препроцессор не заменил PRINT_JOE в #ifdef PRINT_JOE на «ничего»? Макросы осуществляют замену текста только для обычного кода. Другие команды препроцессора игнорируются. Таким образом, PRINT_JOE в #ifdef PRINT_JOE остается как было.

Например:

Область видимости define

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

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

Рассмотрим следующий пример:

function.cpp:

main.cpp:

Результатом программы выше будет:

Not printing!

Несмотря на то, что PRINT был определен в main.cpp, он все равно не имеет никакого влияния на что-либо в function.cpp. Это связано с header guards, о которых мы поговорим в следующем уроке.

В заключение, следует отметить, что директивы, определенные в заголовочном файле могут быть #included в несколько других файлов. То есть, определяем директивы в одном месте и можем использовать в других файлах. Пример этого будет в следующем уроке.

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (31 оценок, среднее: 4,13 из 5)
Загрузка...
Поделиться в:
Подписаться на обновления:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *