Что означает директива #ifndef #define
Содержание:
- definition — ДИРЕКТИВА ПРОГРАММИРОВАНИЕ
- Директива (программирование)
- Язык ассемблера
- Препроцессор Си
- В других языках высокого уровня
- Синтаксис директив [ править | править код ]
- Препроцессор
- история
- Условная компиляция
- Популярные встроенные Angular-директивы
- 7 ответов
- История
- Директива #include
- Директивы #ifdef и #define
- Подводные камни при использовании макросов
- Приватные функции
- Директивы #else и #elif
- Директива препроцессора #IFDEF | #IFNDEF … #ENDIF
- Хорошая практика
- Другие типы макросов
- Нарушение инкапсуляции с Преобразованием типов и Указателями (Плохая практика)
- Инкапсуляция в Си
definition — ДИРЕКТИВА ПРОГРАММИРОВАНИЕ
of Wikipedia
Advertizing ▼
Wikipedia
Директива (программирование)
Материал из Википедии — свободной энциклопедии
Перейти к: ,
У этого термина существуют и другие значения, см. Директива (значения).
В программировании термин «директива» (указание) по использованию похож на термин «команда», так как так же используется для описания некоторых конструкций языка программирования (то есть указаний компилятору или ассемблеру особенностей обработки при компиляции).
Язык ассемблера
В языке ассемблера директивы указывают общую информацию, такую как целевая среда, указание границ между секциями и так далее. Например, директива «ALIGN», которая вставляет в текущую секцию необходимое количество байт для выравнивания строк, часто упоминаемая как «директива», противоречит тому факту, что она совпадает с частями конструкций в генерируемом коде.
Препроцессор Си
В языки программирования Си и C++ встроена поддержка препроцессора. Строки в исходном коде, которые должны быть обработаны препроцессором в виде и называются препроцессорными директивами.
Синтаксические конструкции, похожие на препроцессорные директивы языка Си, такие как в языке C#, также часто называются «директивами», хотя в указанных случаях стадии обработки препроцессором может и не быть.
В других языках высокого уровня
В языке Ada директивы компилятора называются прагмами (сокращение от «pragmatic information» («полезная информация»)).
В Паскале директивы называются указательными комментариями из-за того, что по синтаксису похожи на комментарии. В Паскале указательный комментарий — это комментарий, у которого первым символом указан знак доллара; например, аналогом директивы языка Си будет указательный комментарий .
Синтаксис директив [ править | править код ]
Директивой (командной строкой) препроцессора называется строка в исходном коде, имеющая следующий формат: #ключевое_слово параметры :
- символ # ;
- ноль или более символов пробелов и/или табуляции;
- одно из предопределённых ключевых слов;
- параметры, зависимые от ключевого слова.
Список ключевых слов:
- define — создание константы или макроса;
- undef — удаление константы или макроса;
- include — вставка содержимого указанного файла;
- if — проверка истинности выражения;
- ifdef — проверка существования константы или макроса;
- ifndef — проверка не существования константы или макроса;
- else — ветка условной компиляции при ложности выражения if ;
- elif — проверка истинности другого выражения; краткая форма записи для комбинации else и if ;
- endif — конец ветки условной компиляции;
- line — указание имени файла и номера текущей строки для компилятора;
- error — вывод сообщения и остановка компиляции;
- warning — вывод сообщения без остановки компиляции;
- pragma — указание действия, зависящего от реализации, для препроцессора или компилятора;
- если ключевое слово не указано, директива игнорируется;
- если указано несуществующее ключевое слово, выводится сообщение об ошибке и компиляция прерывается. (В некоторых компиляторах, Таких как g++, компиляция продолжается, просто показывая предупреждение)
Препроцессор
В C и C ++ , язык поддерживает простой макрос препроцессора . Линии источника , которые должны быть обработаны препроцессором, например, #define и #include упоминаются как директивы препроцессора .
Другой C конструкт, то #pragma директива используется для указания компилятора использовать прагматическую или зависящую от реализации функции. Два известных пользователей этой директивы являются OpenMP и OpenACC .
Синтаксические конструкции , аналогичные директивы препроцессора, такие как C # «S #if , также обычно называют„директива“, хотя в этих случаях не может быть никакой реальной фазой предварительной обработки участия.
Все команды препроцессора начинаются с символом хэша (#).
история
Директивы относятся к АЛГОЛУ 68 , где они известны как pragmats (от «прагматичного»), и обозначаются pragmat или пр ; в новых языках, в частности C, это было сокращено до «Прагма» (не «т»).
Обычное использование pragmats в Алголе 68 находится в указании stropping режима, что означает «как ключевые слова указаны». Различные такие директивы следовать, указав POINT, ВЕРХНИЙ, RES (зарезервирован), или цитаты режимов
Обратите внимание на использование stropping для pragmat самого (сокращенно ключевого слова пр ), либо в точке или цитаты режимов:
Сегодня директивы лучше всего известны на языке C, в начале 1970 — х годов урожая, и продолжалась в течение текущего C99 стандарта, где они либо инструкции к C препроцессором , или, в форме #pragma , директивы для самого компилятора. Они также используются в какой — то степени в более современных языках; увидеть ниже.
Условная компиляция
Директивы #if или #ifdef/#ifndef вместе с директивами #elif, #else и #endif управляют компиляцией частей исходного файла.
Если указанное выражение после #if имеет ненулевое значение, в записи преобразования сохраняется группа строк, следующая сразу за директивой #if. Синтаксис условной директивы следующий:
1234567
#if константное выражение группа операций#elif константное выражение группа операций#else группа операций#endif
Отличие директив #ifdef/#ifndef заключается в том, что константное выражение может быть задано только с помощью #define.
У каждой директивы #if в исходном файле должна быть соответствующая закрывающая директива #endif. Между директивами #if и #endif может располагаться любое количество директив #elif, однако допускается не более одной директивы #else. Директива #else, если присутствует, должна быть последней перед директивой #endif.
Пример
1234567891011121314151617
#include <stdio.h>#include <stdlib.h>#define P 2int main(){ system(«chcp 1251»); system(«cls»);#if P==1 printf(«Выполняется ветка 1»);#elif P==2 printf(«Выполняется ветка 2, P=%d», P);#else printf(«Выполняется другая ветка, P=%d», P);#endif getchar(); return 0;}
Результат выполнения
Язык Си
Популярные встроенные Angular-директивы
С помощью директив AngularJS можно создавать пользовательские HTML-теги и атрибуты, чтобы добавить поведение некоторым элементам.
- ng-app
- Объявляет элемент корневым для приложения.
- ng-bind
- Автоматически заменяет текст HTML-элемента на значение переданного выражения.
- ng-model
- То же, что и ng-bind, только обеспечивает двустороннее связывание данных. Изменится содержимое элемента — ангуляр изменит и значение модели. Изменится значение модели — ангуляр изменит текст внутри элемента.
- ng-class
- Определяет классы для динамической загрузки.
- ng-controller
- Определяет JavaScript-контроллер для вычисления HTML-выражений в соответствии с MVC.
- ng-repeat
- Создает экземпляр DOM для каждого элемента из коллекции.
- ng-show и ng-hide
- Показывает или скрывает элемент, в зависимости от значения логического выражения.
- ng-switch
- Создаёт экземпляр шаблона из множества вариантов, в зависимости от значения выражения.
- ng-view
- Базовая директива, отвечает за обработку маршрутов, которые принимают JSON перед отображением шаблонов, управляемых указанными контроллерами.
- ng-if
- Удаляет или создаёт часть DOM-дерева в зависимости от значения выражения. Если значение выражения, назначенного ngIf, равно false, элемент удаляется из DOM, иначе — вновь клонированный элемент вставляется в DOM.
Также существует возможность создавать настраиваемые директивы, используя в том числе шаблоны в теге script.
7 ответов
Boost Preprocessor (который работает для C и C ++, хотя Boost в целом является библиотекой C ++), библиотека может помочь в решении таких задач. Вместо использования #ifdef внутри макроса (что недопустимо) он помогает вам несколько раз включать файл, каждый раз определяя разные макросы, чтобы файл мог использовать #ifdef.
Следующий код, если он сохранен в max.c, должен делать то, что вы хотите, для каждого из слов, перечисленных в MAXES #define в верхней части файла. Однако он не будет работать, если любое из значений _MAX является плавающей точкой, поскольку препроцессор не может обрабатывать плавающую точку.
(Boost Processor — удобный инструмент, но он не совсем прост; вы можете решить, является ли этот подход улучшением по сравнению с копированием и вставкой.)
Я пробовал это раньше. Проблема в том, что уже зарезервировано для преобразования параметров макроса в строку. Он не обрабатывается как токен препроцессора, как в define.
Единственное решение, которое у меня есть, это мошенничество — создайте список типов, у которых в качестве набора определений есть _XXX_MAX, а затем используйте его. Я не знаю, как создать список автоматически в препроцессоре, поэтому я не пытаюсь. Предполагается, что список не слишком длинный и не будет вестись слишком интенсивно.
Я не думаю, что это тот случай, когда оператор ## не разрешен в #ifdef. Я попробовал это:
и он все еще не работал (ему не нравился #ifdef TYPE). Проблема в том, что #ifdef будет принимать только #defined символы, а не #define аргументы. Это две разные вещи.
В отличие от шаблонов, препроцессор не является полным лечением . внутри макроса невозможно. Ваше единственное решение — убедиться, что вы вызываете для типов с совпадающим определено, например . Компилятор обязательно скажет вам, когда это не так.
Нет простого способа сделать это. Самое близкое, что вы можете получить, это #define большое количество макросов IFDEF, таких как:
, так как их нужно много (и они могут быть полезны в нескольких местах), это делает многое
имеет смысл вставлять все это в собственный заголовочный файл ‘ifdefs.h’, который вы можете включить, когда вам это нужно. Вы даже можете написать скрипт, который восстанавливает ifdef.h из
список «макросов интересов»
Затем ваш код становится
Пока вас интересуют только интегральные значения и предполагается, что аппаратное обеспечение использует комплимент 2 и 8-битные байты:
История
Направляющая дата к АЛГОЛУ 68, где они известны как (от «прагматического») и обозначены pragmat или PR; на более новых языках, особенно C, это было сокращено до «pragma» (никакой ‘t’).
Общее использование pragmats в АЛГОЛЕ 68 находится в определении режима правления, означая, «как ключевые слова обозначены». Различный такие директивы следуют, определяя ПУНКТ, ВЕРХНИЙ, RES (зарезервированный), или указывают режимы. Отметьте использование правления для самого pragmat ключевого слова (сокращенный PR), или в ПУНКТЕ или укажите режимы:
.PR УКАЗЫВАЮТ.PR
.PR ВЕРХНИЙ.PR
.PR RES.PR
цитата ‘PR’ ‘PR’
Сегодня директивы являются самыми известными на языке C, начала года изготовления вина 1970-х, и продолжались через текущий стандарт C99, где они — или инструкции к препроцессору C, или, в форме, директивы к самому компилятору. Они также используются до некоторой степени на более новых языках; посмотрите ниже.
Директива #include
Вы уже видели директиву #include в действии. Когда вы подключаете файл с помощью директивы #include, препроцессор копирует содержимое подключаемого файла в текущий файл сразу после строчки с #include. Это очень полезно при использовании определенных данных (например, предварительных объявлений функций) сразу в нескольких местах.
Директива #include имеет две формы:
, которая сообщает препроцессору искать файл в системных путях (в местах хранения системных библиотек языка С++). Чаще всего вы будете использовать эту форму при подключении заголовочных файлов из Стандартной библиотеки C++.
, которая сообщает препроцессору искать файл в текущей директории проекта. Если его там не окажется, то препроцессор начнет проверять системные пути и любые другие, которые вы указали в настройках вашей IDE. Эта форма используется для подключения пользовательских заголовочных файлов.
Директивы #ifdef и #define
#ifndef CUCUMBLER_H #define CUCUMBLER_H /* содержимое файла cucumbler.h */ #endif
Директива #ifndef выполняет проверку не была ли определена константа CUCUMBLER_H ранее, и если ответ отрицательный, то выполняется определение данной константы, и прочего кода, который следует до директивы #endif. Как не сложно догадаться директива #define определяет константу CUCUMBLER_H. В данном случае подобный кусок кода помогает избежать многократного включения одного и того же кода, так как после первого включения проинициализируется константа CUCUMBLER_H и последующие проверки #ifndef CUCUMBLER_H будут возвращать FALSE.
Директива #define широко применяется и при отладке программы.
#include <iostream> #include <string> #include <vector> using namespace std; int main() { #ifdef IN_DEBUG cout << "Начало функции main()\n"; #endif string text; vector<string> text_array; while ( cin >> text ) { #ifdef IN_DEBUG cout << "Прочитан текст: " << text << "\n"; #endif text_array.push_back(text); }
return 0;
}
Если константа IN_DEBUG не задана, то препроцессор сгенерирует следующий исходник:
#include <iostream> #include <string> #include <vector> using namespace std; int main() { string text; vector<string> text_array; while ( cin >> text ) { text_array.push_back(text); } return 0; }
Но если определить IN_DEBUG, то текст программы кардинальным образом поменяется
#include <iostream> #include <string> #include <vector> using namespace std; int main() { cout << "Начало функции main()\n"; string text; vector<string> text_array; while ( cin >> text ) { cout << "Прочитан текст: " << text << "\n"; text_array.push_back(text); } return 0; }
Задать препроцессорную константу можно прямо из консоли. Например для компилятора g++ применяется следующий формат
$g++ -D IN_DEBUG ./main.cpp.
Подводные камни при использовании макросов
Отсутствие скобок
Наиболее распространённая ошибка, которую допускают при использовании макросов, — отсутствие скобок вокруг аргументов в определениях макросов. Поскольку макросы подставляются непосредственно в код, это может вызвать неприятные побочные эффекты:
В этом примере выполняется вычисление и ожидаемый результат — 50. Но в процессе подстановки произойдёт следующее преобразование:
Как несложно подсчитать, данное выражение выдаст не 50, а 30.
А вот как выполнить данную задачу правильно:
Инкремент и декремент
Допустим, есть такой код:
Здесь можно ожидать, что будет увеличен на единицу и будет равен 6, а результат — 5. Но вот что получится в реальной жизни:
Виновата всё та же макроподстановка:
Как видно, увеличивается на единицу в первый раз при проверке и во второй раз при определении результата, что и приводит к соответствующим итогам.
Передача вызовов функций
Использованием функции в коде никого не удивишь. Равно как и передачей результата одной функции в виде аргумента для другой. Часто это делается так:
И в этой вставке кода всё в порядке. Но, когда это же производится с помощью макроса, можно столкнуться с серьёзными проблемами производительности. Допустим, есть вот этот код:
Здесь определена рекурсивная функция . Она вызывается один раз для первой строки () и другой раз — для второй (). Но, если передать вызовы функций, как аргументы для макроса, будет выполнено три рекурсивных вызова вместо двух. Для больших структур данных это станет узким местом производительности. Особенно, если макрос используется внутри рекурсивной функции.
Многострочные макросы
Программисты не всегда используют фигурные скобки вокруг единичных команд в циклах и условных операторах. Но, если произвести макроподстановку внутри этого блока, и этот макрос будет содержать несколько строк, это приведёт к весьма интересным результатам:
Во вставке кода выше нет фигурных скобок в первом цикле и, так как макрос заменяет одну строку несколькими, только первое выражение в макросе выполняется в цикле. Следовательно, это приведёт к бесконечному циклу, так как никогда не увеличится.
Прим. перев. Эту проблему также можно решить с помощью упомянутого выше трюка с .
Именно из-за таких особенностей многие стараются избегать использования макросов.
Приватные функции
Функции, будучи внешними () по умолчанию, видимы во всей так называемой единице трансляции (). Другими словами, если несколько файлов скомпилированы вместе в один объектный файл, любой из этих файлов сможет получить доступ к любой функции из любого другого файла. Использование ключевого слова “статический” () при создании функции ограничит ее видимость до файла в котором она была определена.Следовательно, для обеспечения приватности функции необходимо выполнить несколько шагов:
- функция должна быть объявлена статической () либо в исходном файле (.c), либо в соответствующем заголовочном файле (.h);
- определение функции должно находиться в отдельном исходном файле.
В данном примере, в файле “private_funct.c”, была определена статическая функция . К слову, функция успешно вызывает поскольку они находятся в одном файле.
В соответствующем заголовочном файле «private_funct.h», была декларирована как статическая функция.
Основная программа, “main.c”, успешно вызывает опосредовательно через , поскольку обе функции находятся в одном документе. Тем не менее, попытка вызвать из основной программы вызовет ошибку.
Директивы #else и #elif
Директива #else действует аналогично условному оператору else языка C#, определяя альтернативный ход выполнения программы, если этого не может сделать директива #if.
Обозначение #elif означает «иначе если», а сама директива #elif определяет последовательность условных операций if-else-if для многовариантной компиляции.
После директивы #elif указывается идентификаторное выражение. Если это выражение истинно, то компилируется следующий далее кодовый блок, а остальные выражения директивы #elif не проверяются. В противном случае проверяется следующий по порядку блок. Если же ни одну из директив #elif не удается выполнить, то при
наличии директивы #else выполняется последовательность кода, связанная с этой директивой, а иначе не компилируется ни один из кодовых блоков директивы #if.
Ниже приведена общая форма директивы #elif:
#if идентификаторное_выражение последовательность операторов #elif идентификаторное_выражение последовательность операторов #elif идентификаторное_выражение последовательность операторов //... #endif
Давайте добавим в предыдущий пример следующий код:
Директива препроцессора #IFDEF | #IFNDEF … #ENDIF
Выполняет условное включение группы команд в код на этапе компиляции, если определена константа этапа компиляции.
Синтаксис
#IFDEF | #IFNDEF ConstantName Commands #ENDIF
Параметры#IFDEF
Указывает, что на этапе компиляции следует включить заданную группу команд, если была определена константа этапа компиляции с именем ConstantName.
Ниже описывается, каким образом группа команд включается в код на этапе компиляции при задании #IFDEF.
Если константа ConstantName определена, на этапе компиляции включается группа команд, следующая за #IFDEF и предшествующая директиве #ELSE или #ENDIF (той из них, которая встретится раньше).
Если константа ConstantName не определена и задана директива #ELSE, на этапе компиляции включается группа команд, следующая за #ELSE и предшествующая директиве #ENDIF.
Если константа ConstantName не определена и директива #ELSE не задана, ни одна из команд, находящихся внутри структуры #IFDEF … #ENDIF, не включается на этапе компиляции.#IFNDEF
Указывает, что на этапе компиляции следует включить заданную группу команд, если константа этапа компиляции с именем ConstantName не определена.
Ниже описывается, каким образом группа команд включается в код на этапе компиляции при задании #IFNDEF.
Если константа ConstantName не определена, на этапе компиляции включается группа команд, следующих за #IFNDEF и предшествующих директиве #ELSE или #ENDIF (той из них, которая встретится раньше).
Если константа ConstantName определена и задана директива #ELSE, на этапе компиляции включается группа команд, следующих за #ELSE и предшествующих директиве #ENDIF.
Если константа ConstantName определена и директива #ELSE не задана, ни одна из команд, находящихся внутри структуры #IFDEF … #ENDIF, не включается на этапе компиляции.ConstantName
Задает константу этапа компиляции, существование которой определяет необходимость включения группы команд на этапе компиляции. Константы этапа компиляции определяются с помощью директивы #DEFINE.Commands
Задают группу команд, включаемых на этапе компиляции.
Комментарии
Структуру #IFDEF | #IFNDEF … #ENDIF можно вкладывать в другую структуру #IFDEF | #IFNDEF … #ENDIF.
Комментарии можно располагать в одной строке с директивами #IFDEF, #IFNDEF, #ELSE и #ENDIF. Комментарии игнорируются во время компиляции и выполнения программы.
Пример
* В следующем примере создается константа этапа компиляции с именем * MYDEFINE. Структура #IFDEF ... #ENDIF задает выдачу сообщения о том, * была ли определена константа этапа компиляции. #DEFINE MYDEFINE 1 #IFDEF MYDEFINE WAIT WINDOW "MYDEFINE exists" #ELSE WAIT WINDOW "MYDEFINE does not exist" #ENDIF
Предыдущий Шаг | Следующий Шаг | ОглавлениеАвтор Каев Артем.
Хорошая практика
Чтобы свести к минимуму проблемы, вызванные использованием макросов, хорошей практикой будет использование единого подхода для определения макросов в вашем коде. Каким будет этот подход, не имеет значения. Есть проекты, в которых все макроопределения объявлены в верхнем регистре. В некоторых проектах в начале имени макроса используют букву «m». Выберите себе любой подход, но этот подход должен быть таким, чтобы и вы, и другой программист, который будет работать с вашим кодом, сразу понимал, что имеет дело с макросами.
Прим. перев. Другие полезные практики оформления кода можно посмотреть в нашей статье.
Другие типы макросов
Помимо макросов, которые подменяют функции, есть и другие очень полезные директивы препроцессора. Вот некоторые из наиболее часто используемых:
- — включить содержимое стороннего файла в текущий файл,
- — задать условие для компиляции,
- — определить константу (и, конечно же, макрос).
играет ключевую роль при создании заголовочных файлов. Использование этого макроса гарантирует, что заголовочный файл включён только один раз:
Стоит отметить, что основное предназначение — условная компиляция блоков кода на основе некоторого условия. Например, вывод отладочной информации только в режиме отладки:
Прим. перев. С помощью также довольно часто задаётся условие для компиляции на различных версиях ОС и архитектурах: , , и так далее.
Как правило, для определения констант используется , но в некоторых проектах его заменяют на и перечисления (). Однако при использовании любой из этих альтернатив есть свои преимущества и недостатки.
Использование ключевого слова , в отличие от макроподстановки, позволяет произвести проверку типов данных. Но в Cи это создаёт не совсем полноценные константы. Например, их нельзя использовать в операторе и для определения размера массива.
Прим. автора В C++ переменные, определённые ключевым словом , являются полноценными константами (их можно использовать в приведённых выше случаях), и настоятельно рекомендуется использовать именно их, а не .
Перечисления в то же время — полноценные константы. Они могут использоваться в операторах и для определения размера массива. Однако их недостаток заключается в том, что в перечислениях можно использовать только целые числа. Их нельзя использовать для строковых констант и констант с плавающей запятой.
Вот почему использование — оптимальный вариант, если нужно достичь единообразия в определении различного рода констант.
Таким образом, у макросов есть свои преимущества. Но использовать их нужно весьма аккуратно.
Нарушение инкапсуляции с Преобразованием типов и Указателями (Плохая практика)
Прежде всего, стоит заметить что использовать указатели и преобразование типов таким способом — плохая идея. Этот способ не гарантирует получения нужных данных. Он плохо читается и плохо поддерживается. Невзирая на это, он существует.
Си++ получил в наследство от Си множество инструментов, один из которых — преобразование типов (). По умолчанию, все переменные и методы в классе приватные. В то же время, стандартный уровень доступа к данным в структуре () — публичный. Возможно создать структуру или полностью публичный класс в котором данные будут расположены идентично данным в классе и используя преобразование типов получить доступ к приватным данным.
Приватные данные были прочитаны и изменены благодаря преобразованию типов
Инкапсуляция в Си
Традиционно считается что инкапсуляция — один из ключевых ООП принципов. Тем не менее, это не лимитирует использование этого принципа в процедурно-ориентированных языках. В Си, инкапсуляция используется давно, невзирая на отсутствие ключевых слов “приватный” и “публичный”.