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

   ⁄ 

 Обновлено 5 мая 2018  ⁄ 

 ⁄   13 

⁄   6515

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

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 (#define PRINT), это всё равно не имеет никакого влияния на что-либо в function.cpp. Поэтому при выполнении функции doSomething() у нас срабатывает Not printing!, так как в файле function.cpp мы PRINT не объявляли (не использовали директиву #define). Это связано с header guards, о которых мы поговорим в следующем уроке.

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

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

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

Комментариев: 13

  1. илья:

    а мы будем проходить работу с графикой в программировании?

    1. Юрий:

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

      1. илья:

        ПОНЯТНО,спасибо

  2. Дима:

    Сложновато(
    Пока только начинаем изучать, то вернемся попоже)

  3. Михаил:

    тяжко заходит эта тема с дерективами (((

    1. Михаил:

      есть может быть варианты как попроще объяснить эту тему? А то к меня шарики за ролики заехали. Эта тема важна, поэтому хочу в ней разобраться,чтобы не было пробелов

      1. Юрий:

        Перечитайте статью несколько раз, поищите дополнительно информацию в Интернете, поищите в YouTube, загляните в буржунет (если знаете английский), спросите у знакомых программистов, чтобы объяснили вам детальнее, скачайте книги по C++ — посмотрите в них информацию насчет директив препроцессора. Или, как вариант, мне переписать статью полностью?

  4. Максим():

    Да уж, тема сложновата… Придется посидеть…

    1. painkiller:

      Да, мне сперва тоже так показалось, но после повторного прочтения всё стало на свои места.
      В предыдущем уроке оставил несколько вопросов в комментариях, но благодаря этому уроку ответов на них уже не требуется — до всего дошел сам.

  5. Сергей:

    Что-то вообще не понятно для чего эта ерунда нужна. Где она используется, для чего она? Лично мне видится, что она существует чтобы просто была.

    1. Юрий:

      Если не увидели или не поняли смысла — значит его нет.

  6. Vlados:

    Несмотря на то, что PRINT был определен в main.cpp, он все равно не имеет никакого влияния на что-либо в function.cpp.

    PRINT был объявлен же, а не определён.
    Иди я что-то путаю

    1. Юрий:

      Исправил. В этом случае директива #defined используется как в качестве объявления, так и определения. Т.е. это одно и то же (в этом случае).

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

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

ПОДПИСЫВАЙТЕСЬ

НА КАНАЛ RAVESLI В TELEGRAM

@ravesli

ПОДПИСАТЬСЯ БЕСПЛАТНО