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

  Юрий Ворон  | 

    | 

  Обновлено 5 Ноя 2018  | 

 16081

 ǀ   18 

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

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 (148 оценок, среднее: 4,50 из 5)
Загрузка...

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

  1. Oleksiy:

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

  2. Oleksiy:

    Подтверждаю, эта глава написана не очень доходчиво для новичка. Может, проблема со стилем? Читатель (я) постоянно задает вопрос: а зачем эти директивы нужны? Нет мотивации схватить материал за рога. Буду перечитывать еще раз. С предыдущими главами такого не было.

  3. Вячеслав:

    Директивы, определенные в одном файле кода, не влияют на код других файлов того же проекта. Не согласен, а если в файле function.cpp у нас будет написано #define PRINT, а в файле main.cpp напишем #include "function.cpp" — PRINT будет влиять на код в обеих файлах. Все зависит от порядка подключения. А определения передаваемые в аргументы компилятора видны, вообще, всем файлам проекта.

  4. Алексей:

    Здравствуйте. Можно ли определить с помощью команды к примеру #define MY_DEFINE в файле main.cpp в файле add.cpp?

    1. Юрий Юрий:

      В файле main.cpp в файле add.cpp? Я что-то не понял, что вы имеете в виду. MY_DEFINE можно определить только один раз и в одном файле проекта.

  5. илья:

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

    1. Юрий Юрий:

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

      1. илья:

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

  6. Дима:

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

  7. Михаил:

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

    1. Михаил:

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

      1. Юрий Юрий:

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

  8. Максим():

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

    1. painkiller:

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

  9. Сергей:

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

    1. Юрий Юрий:

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

  10. Vlados:

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

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

    1. Юрий Юрий:

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

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

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

telegram канал
RAVESLI