- Включение исходных файлов (#include)
- Обзор директив макроподстановки
- Простая форма #define
- Форма #define в виде псевдо-функции
- Специальные операторы '#' и '##' внутри определений #define
- Отмена макроподстановки (#undef)
- Предопределенные константы препроцессора
- Условная компиляция (#ifdef/#ifndef/#else/#endif)
- Общие свойства программ (#property)
Форма #define в виде псевдо-функции
Синтаксис параметрической формы #define напоминает функцию.
#define макро_идентификатор(параметр,...) текст_с_параметрами |
Такой макрос имеет один или несколько параметров в круглых скобках. Параметры разделены запятыми. Каждый параметр — это простой идентификатор (часто однобуквенный). Причем у всех параметров одного макроса идентификаторы должны различаться.
Важно, чтобы между идентификатором и открывающей круглой скобкой не было пробела — иначе макрос будет воспринят как простая форма, в которой текст для замены начинается с открывающей круглой скобки.
После регистрации данной директивы препроцессор будет искать в исходных кодах строки вида:
макро_идентификатор(выражение,...) |
В них вместо параметров могут быть указаны произвольные выражения. Количество аргументов должно совпадать с количеством параметров макроса. Все найденные вхождения будут заменены на текст_с_параметрами, в котором, в свою очередь, параметры будут заменены на переданные выражения. Каждый параметр может встретиться несколько раз, в произвольном порядке.
Например, следующий макрос позволяет найти максимальное из двух значений:
#define MAX(A,B) ((A) > (B) ? (A) : (B)) |
Если в коде будет инструкция:
int z = MAX(x, y); |
она "раскроется" препроцессором в:
int z = ((x) > (y) ? (x) : (y)); |
Макроподстановка будет работать для любых типов данных (для которых допустимы примененные внутри макроса операции).
Вместе с тем, подстановка может иметь и побочные эффекты. Например, если в качестве фактического параметра указан вызов функции или инструкция с модификацией переменной (скажем, ++x), то соответствующее действие может быть выполнено несколько раз (вместо предполагаемого одного раза). В случае MAX это случится дважды: во время сравнения и при получении значений в одной из веток оператора '?:'. В связи с этим имеет смысл по возможности преобразовывать подобные макросы в функции (особенно учитывая, что в MQL5 для функций автоматически применяется инлайнинг).
Обратите внимание на круглые скобки вокруг параметров и вокруг всего определения макроса. Они нужны, чтобы гарантировать, что подстановка выражений в качестве параметров или самого макроса внутрь других выражений не исказит порядок вычислений в силу разных приоритетов. Допустим, макрос определяет умножение двух параметров (пока не заключенных в скобки):
#define MUL(A,B) A * B |
Тогда использование макроса со следующими выражениями приведет к неожиданному результату:
int x = MUL(1 + 2, 3 + 4); // 1 + 2 * 3 + 4 |
Вместо умножения (1 + 2) * (3 + 4), что дает 21, мы получили 1 + 2 * 3 + 4, то есть 11. Свободное от этой проблемы определение макроса должно быть таким:
#define MUL(A,B) ((A) * (B)) |
В качестве параметра макроса можно указать другой макрос. Кроме того, в определении макроса также можно вставлять другие макросы. Все такие макросы будут подменяться последовательно. Например:
#define SQ3(X) (X * X * X)
|
Тогда следующий код выведет 504 (MathAbs — встроенная функция возвращающая модуль числа, т.е. без знака):
int x = -10;
|
В переменой x при этом останется значение -7 (за счет троекратного инкремента).
Определение макроса может содержать непарные скобки. Такой прием используется, как правило, в паре макросов, один из которых должен открывать некий фрагмент кода, а другой закрывать. При этом непарные в каждом из них скобки станут парными. В частности, в файлах стандартной библиотеки из поставки MetaTrader 5, в Controls/Defines.mqh определены макросы EVENT_MAP_BEGIN и EVENT_MAP_END. С помощью них формируется функция обработки событий в графических объектах.
Напомним, что препроцессор читает весь исходный текст программы строка за строкой, начиная с основного mq5-файла и вставляя по месту тексты из встретившихся заголовочных файлов. К моменту чтения любой строки кода формируется некий набор макросов, которые уже определены. При этом не важно, в какой последовательности макросы были определены: вполне допустимо, что один макрос ссылается в своем определении на другой, который был описан как выше, так и ниже по тексту. Важно лишь, чтобы в той строке исходного кода, где используется имя макроса, были известны определения всех макросов, на которые есть ссылки.
Рассмотрим пример.
#define NEG(x) (-SQN(x))*TEN
|
Здесь макрос NEG использует макросы SQN и TEN, которые описаны ниже него. И это не мешает успешно применять его в коде после всех трех #define-ов.
Однако если изменить относительное положение строк на следующее:
#define NEG(x) (-SQN(x))*TEN
|
получим ошибку компиляции "неизвестный идентификатор" ("undeclared identifier").