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

  Юрий  | 

    | 

  Обновл. 10 Мар 2019  | 

 27812

 ǀ   24 

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

Директива #include

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

Директива #include имеет две формы:

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

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

Директива #define


Директиву #define можно использовать для создания макросов. Макрос — это правило, которое определяет конвертацию идентификатора в указанные данные.

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

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

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

#define identifier
#define identifier substitution_text

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

Макросы-объекты с substitution_text

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

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

Препроцессор преобразует код выше в:

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

My favorite number is: 9

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

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


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

Любое дальнейшее появление идентификатора USE_YEN удаляется и заменяется «ничем» (пустым местом)!

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

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

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

   #ifdef;

   #ifndef;

   #endif.

Директива #ifdef (англ. «if defined» = «если определено») позволяет препроцессору проверить, было ли значение ранее #define. Если да, то код между #ifdef и #endif скомпилируется. Если нет, то код будет проигнорирован. Например:

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

Директива #ifndef (англ. «if not defined» = «если не определено») — это полная противоположность #ifdef, которая позволяет проверить, не было ли значение ранее определено. Например:

Результатом выполнения этого фрагмента кода будет Bob, так как PRINT_BOB ранее никогда не был #define. Условная компиляция очень часто используется в качестве header guards (о них мы поговорим в следующем уроке).

Область видимости директивы #define


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

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

Несмотря на то, что директива #define MY_NAME "Alex" определена внутри функции boo, препроцессор этого не заметит, так как он не понимает такие понятия C++, как функции. Следовательно, выполнение этой программы будет идентично той, в которой бы #define MY_NAME "Alex" было определено ДО, либо сразу ПОСЛЕ функции boo. Для лучше читабельности кода определяйте идентификаторы (с помощью #define) вне функций.

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

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

function.cpp:

main.cpp:

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

Not printing!

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

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

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (247 оценок, среднее: 4,58 из 5)
Загрузка...

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

  1. Аватар Никита:

    Че так годно то?

  2. Аватар Алексей:

    В предпоследнем примере вы утверждаете, что препроцессор не знает что такое функция и внутри неё работать не будет, а в конце статьи мы видим обратное. Что же верно?

    1. Юрий Юрий:

      Верно то, что написано в уроке. Пройдите ещё с 10-ок уроков и возвратитесь к этому уроку и перечитайте его. Возможно, станет понятнее.

  3. Аватар Vl:

    Насколько я понимаю, суть может быть вот в чем (очень приближенно). Вот у вас программа пишется для 64 бит системы и для 32. Но в сам код это вставлять не хочется. Пускай препроцессор сам выбирает тип компиляции в зависимости от системы. То есть на одном этапе надо так скомпилировать программу, а на другом эдак. Но получилось так, что мы можем использовать это как переменные)))

  4. Аватар Сергей:

    Компилируется только без " std::cout << "Not printing!"; "
    Как только пишу эту строчку компилятор выдает ошибку " error C2065: cout: необъявленный идентификатор ". В чем же проблема?

  5. Аватар Oleksiy:

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

  6. Аватар Oleksiy:

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

  7. Аватар Вячеслав:

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

  8. Аватар Алексей:

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

    1. Юрий Юрий:

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

  9. Аватар илья:

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

    1. Юрий Юрий:

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

      1. Аватар илья:

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

  10. Аватар Дима:

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

  11. Аватар Михаил:

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

    1. Аватар Михаил:

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

      1. Юрий Юрий:

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

        1. Аватар Taueron:

          Как вариант переписать полностью, раз уж взялись. Спасибо за Ваши труды.

  12. Аватар Максим():

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

    1. Аватар painkiller:

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

  13. Аватар Сергей:

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

    1. Юрий Юрий:

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

  14. Аватар Vlados:

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

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

    1. Юрий Юрий:

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

Добавить комментарий для painkiller Отменить ответ

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