Что такое директива в c
Перейти к содержимому

Что такое директива в c

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

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

Директива #include

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

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

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

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

Директива #define

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

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

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

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

#define идентификатор текст_замена

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

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

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

2.9 – Знакомство с препроцессором

Когда вы компилируете свой код, вы можете ожидать, что компилятор компилирует код именно в том виде, как вы его написали. На самом деле это не так.

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

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

Когда препроцессор запускается, он просматривает файл кода (сверху вниз) в поисках директив препроцессора. Директивы препроцессора (часто называемые просто директивами) – это инструкции, которые начинаются с символа # и заканчиваются символом новой строки (НЕ точкой с запятой). Эти директивы сообщают препроцессору, что нужно выполнить определенные задачи по обработке текста. Обратите внимание, что препроцессор не понимает синтаксис C++ – вместо этого директивы имеют свой собственный синтаксис (который иногда напоминает синтаксис C++, а иногда не очень).

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

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

В качестве отступления.

Директивы using (представленные в уроке «2.8 – Конфликты имен и пространства имен») не являются директивами препроцессора (и, следовательно, препроцессором не обрабатываются). Таким образом, хотя термин директива обычно означает директиву препроцессора, это не всегда так.

Включения

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

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

Когда препроцессор запускается для этой программы, он заменяет #include <iostream> предварительно обработанным содержимым файла с именем " iostream ".

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

Определения макросов

Директива #define может использоваться для создания макросов. В C++ макрос – это правило, определяющее, как входной текст преобразуется в выходной текст с помощью замены.

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

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

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

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

Макросы, подобные объектам, с подставляемым текстом

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

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

Препроцессор преобразует приведенный выше код в следующее:

Этот код при запуске печатает: My name is: Alex .

Объектоподобные макросы использовались как более дешевая альтернатива постоянным переменным. Те времена давно прошли, поскольку компиляторы стали умнее, а язык вырос. Теперь объектоподобные макросы можно увидеть только в устаревшем коде.

Мы рекомендуем вообще избегать таких макросов, так как существуют более эффективные способы сделать аналогичные вещи. Мы обсудим это более подробно в уроке «4.14 – const, constexpr и символьные константы».

Макросы, подобные объектам, без подставляемого текста

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

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

Это может показаться довольно бесполезным, и, да, это бесполезно для замены текста. Однако эта форма директивы обычно используется для другого. Мы обсудим использование этой формы чуть позже.

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

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

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

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

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

Поскольку PRINT_JOE был определен с #define , строка cout << "Joe\n" будет скомпилирована. Поскольку PRINT_BOB не был определен с #define , строка cout << "Bob\n" будет проигнорирована.

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

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

Вместо #ifdef PRINT_BOB и #ifndef PRINT_BOB вы также можете увидеть #if defined (PRINT_BOB) и #if !defined(PRINT_BOB) . Они делают то же самое, но используют синтаксис, немного более похожий на C++.

Еще одно распространенное использование условной компиляции включает использование #if 0 для исключения блока кода из компиляции (как если бы он находился внутри блока комментариев):

Приведенный выше код печатает только « Joe », потому что « Bob » и « Steve » находились внутри блока #if 0 , который препроцессор исключил из компиляции.

Это обеспечивает удобный способ «закомментировать» код, содержащий многострочные комментарии.

Макросы, подобные объектам, не влияют на другие директивы препроцессора.

Теперь вам может быть это интересно:

Поскольку мы определили PRINT_JOE как ничто, почему препроцессор не заменил PRINT_JOE в #ifdef PRINT_JOE ничем?

Макросы вызывают замену текста только в обычном коде. Другие команды препроцессора игнорируются. Следовательно, PRINT_JOE в #ifdef PRINT_JOE не меняется.

На самом деле вывод препроцессора вообще не содержит директив – все они разрешаются/удаляются перед компиляцией, потому что компилятор не знает, что с ними делать.

Область видимости определений #define

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

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

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

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

Директивы препроцессора в Си

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

Основные директивы препроцессора

#include — вставляет текст из указанного файла
#define — задаёт макроопределение (макрос) или символическую константу
#undef — отменяет предыдущее определение
#if — осуществляет условную компиляцию при истинности константного выражения
#ifdef — осуществляет условную компиляцию при определённости символической константы
#ifndef — осуществляет условную компиляцию при неопределённости символической константы
#else — ветка условной компиляции при ложности выражения
#elif — ветка условной компиляции, образуемая слиянием else и if
#endif — конец ветки условной компиляции
#line — препроцессор изменяет номер текущей строки и имя компилируемого файла
#error — выдача диагностического сообщения
#pragma — действие, зависящее от конкретной реализации компилятора.

Директива #include

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

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

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