- Включение исходных файлов (#include)
- Обзор директив макроподстановки
- Простая форма #define
- Форма #define в виде псевдо-функции
- Специальные операторы '#' и '##' внутри определений #define
- Отмена макроподстановки (#undef)
- Предопределенные константы препроцессора
- Условная компиляция (#ifdef/#ifndef/#else/#endif)
- Общие свойства программ (#property)
Простая форма #define
Простая форма директивы #define регистрирует идентификатор и последовательность символов, на которую этот идентификатор следует заменить везде в исходных кодах после директивы, вплоть до конца программы или до директивы #undef с тем же идентификатором.
Её синтаксис таков:
#define макро_идентификатор [текст] |
Текст начинается после идентификатора и продолжается до конца текущей строки. Идентификатор и текст должно разделять произвольное количество пробелов или табуляций. Если требуемая последовательность символов слишком длинная, то для удобочитаемости можно переносить её на следующие строки, ставя в конце прерываемой строки символ обратной косой черты '\'.
#define макро_идентификатор текст_начало \
|
Текст может состоять из любых конструкций языка: констант, операторов, идентификаторов, знаков пунктуации. После подстановки в исходный код вместо найденных упоминаний макро_идентификатора, все они будут участвовать в компиляции.
Простая форма традиционно используется для:
- объявления флагов, которые затем используются для проверок условной компиляции;
- объявления именованных констант;
- сокращенной записи расхожих инструкций.
Первый пункт характерен тем, что после идентификатора можно ничего не указывать — наличия директивы с именем уже достаточно, чтобы соответствующий идентификатор был зарегистрирован и мог использоваться в условных директивах #ifdef/ifndef. Для них важно лишь, существует идентификатор или нет, то есть он работает в режиме флага: объявлен/не объявлен. Например, нижеприведенная директива определяет флаг DEMO:
#define DEMO |
Его затем можно применять, предположим, для сборки демо-версии программы, из которой исключаются некоторые функции (см. пример в разделе условной компиляции).
Второй вариант использования простой директивы позволяет заменить в исходном коде "магические числа" понятными именами. "Магическими числами" называют вставленные в исходный текст константы, смысл которых не всегда ясен (потому что число — это просто число: его желательно хотя бы пояснить в комментарии). Кроме того, одно и то же значение может быть разбросано по разным частям кода, и если программист решит изменить его на другое, то ему придется делать это во всех местах (и надеяться, что он ничего не упустил).
При наличии макроса с именем эти две проблемы легко решаются. Например, скрипт может подготавливать массив с числами ряда Фибоначчи на некую максимальную глубину. Тогда имеет смысл определить макрос с предопределенным размером массива и использовать его в описании самого массива (Preprocessor.mq5).
#define MAX_FIBO 10
|
Если программист впоследствии решит, что размер массива нужно увеличить, ему достаточно сделать это в одном месте — в директиве #define. Таким образом, директива фактически определяет некий параметр алгоритма, который "зашит" в исходный код и недоступен для настройки пользователю. Потребность в этом возникает достаточно часто.
Может возникнуть вопрос, чем определение через #define отличается от переменной-константы в глобальном контексте. Действительно, мы могли бы описать переменную с таким же именем и назначением, и даже сохранить верхний регистр букв:
const int MAX_FIBO = 10; |
Однако в этом случае MQL5 не позволит определить массив с указанным размером, так как в квадратных скобках допускаются только константы, т.е. литералы (а переменная-константа, несмотря на свое похожее название, константой не является). Чтобы решить эту проблему, мы могли бы определить массив как динамический (без предварительного указания размера) и затем выделять для него память с помощью функции ArrayResize — здесь сложностей с передачей переменной в качестве размера не возникает.
Альтернативный способ определить именованную константу предоставляют перечисления, но он ограничен только целочисленными значениями. Например:
enum
|
Макрос же способен содержать значение любого типа.
#define TIME_LIMIT D'2023.01.01'
|
Поиск имен макросов в исходных текстах для замены производится с учетом синтаксиса языка, то есть неделимые элементы, такие как идентификаторы переменных или строковые литералы останутся без изменений, даже если в их состав входит подстрока, совпадающая с одним из макросов. Например, с учетом нижеприведенного макроса XYZ, переменная XYZAXES сохранится как есть, а имя XYZ (поскольку оно целиком совпадает с макросом) будет заменено на ABC.
#define XYZ ABC
|
С помощью макроподстановок можно встраивать свой код в исходный текст других программ. Обычно этот прием используется библиотеками, которые распространяются в виде заголовочных mqh-файлов и подключаются к программам с помощью директив #include.
В частности, для скриптов мы можем определить свою собственную, библиотечную реализацию функции OnStart, которая должна выполнить некие дополнительные действия, не затронув при этом исходный функционал программы.
void OnStart()
|
Допустим, эта часть находится в подключаемом заголовочном файле (Preprocessor.mqh).
Тогда оригинальная функция OnStart (в Preprocessor.mq5) будет переименована препроцессором в исходном коде на _OnStart (подразумевается, что там этот идентификатор не используется еще где-то по другому назначению). А новая версия OnStart из заголовка вызывает _OnStart, "обернув" её в дополнительные инструкции.
Третий распространенный способ использования простого #define — сокращение записи языковых конструкций. Например, заголовок бесконечного цикла можно обозначить одним словом LOOP:
#define LOOP for( ; !IsStopped() ; ) |
И потом применять в коде:
LOOP
|
Данный способ также является основным приемом использования директивы #define с параметрами (см. далее).