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



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

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

Директива #include

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

Существует две разновидности директивы include:

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

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

Директива #define

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

Существуют два основных вида макросов: макросы-функции и макросы-объекты.

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

Существует два способа определения макросов-объектов:

определить уникальный идентификатор

Или же:

определить идентификатор текст_замена

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

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

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

Давайте изучим данный участок программного кода:

define MY_FAVORITE_NUMBER 9

std :: cout << "My favorite number is: " << MY_FAVORITE_NUMBER << std :: endl ;

Программа изменяет указанный выше код на:

std :: cout << "My favorite number is: " << 9 << std :: endl ;

Итог выполнения:

My favorite number is: 9

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

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

Также возможно определение макросов-объектов без использования текстовой замены, например:

define USE_YEN

Если вы встретите идентификатор USE_YEN еще раз, его следует удалить и заменить на пустое место!

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

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

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

Инструкция ifdef (сокращение от "if defined" = "если определено") позволяет препроцессору проверить, было ли значение ранее определено с помощью директивы define. Если это так, то код между ifdef и endif будет скомпилирован. В противном случае код будет проигнорирован. Пример использования:

define PRINT_JOE

ifdef PRINT_JOE

std :: cout << "Joe" << std :: endl ;

endif

ifdef PRINT_BOB

std :: cout << "Bob" << std :: endl ;

endif

Так как переменная PRINT_JOE уже была объявлена, то команда std::cout << "Joe" << std::endl; будет успешно скомпилирована и выполнена. В то же время, поскольку переменная PRINT_BOB не была определена, то команда std::cout << "Bob" << std::endl; не будет скомпилирована и, как следствие, не выполнится.

Директива ifndef (сокращение от "if not defined" = "если не определено") представляет собой альтернативу директиве ifdef, которая позволяет проверить, не было ли значение определено ранее. Например:

ifndef PRINT_BOB

std :: cout << "Bob" << std :: endl ;

endif

После выполнения этого участка кода будет получено значение Bob, так как PRINT_BOB ранее не был определен. Часто условная компиляция используется в качестве защиты заголовочных файлов (об этом будет рассказано на следующем занятии).

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

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

#include

void boo ( )

{

#define MY_NAME "Alex"

}

int main ( )

{

std :: cout << "My name is: " << MY_NAME ;

return 0 ;

}

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

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

Давайте рассмотрим пример:

Файл function.cpp:

#include

void doSomething ( )

{

#ifdef PRINT

std :: cout << "Printing!" ;

#endif

#ifndef PRINT

std :: cout << "Not printing!" ;

#endif

}

Файл main.cpp:

Объявление функции doSomething() без параметров;

function main()

Открывающий тег

Определить функцию PRINT.Выполнить действие ( ) ;

возвращать 0;

Необходимо изменить текст и сделать его оригинальным.

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

Unable to print!

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

166   0  

Comments

    Ничего не найдено.