Базовые понятия

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

Прежде всего, по количеству требуемых операндов операторы делятся на унарные и бинарные. Как можно понять по этим названиям, унарные обрабатывают один операнд, а бинарные — два. В случае бинарных, оператор всегда располагается между операндами. Среди унарных встречаются операторы, которые должны стоять перед операндом, и такие, которые стоят после. Например, оператор унарного минуса ('-') позволяет поменять знак величины на противоположный:

int x = 10;
int y = -x;  // -10

При этом существует также и бинарный оператор вычитания, использующий тот же символ '-'.

int z = x - y// 10 - -10 -> 20

Выбор компилятором правильного оператора (действия) в конкретном случае определяется контекстом его применения в выражении.

Каждому оператору присвоен приоритет. Он определяет порядок, в котором операторы рассчитаются в сложных выражениях, где имеется более одного оператора. Операторы с более высоким приоритетом рассчитываются первыми, а с более низким — последними. Например, в выражении 1 + 2 * 3 есть две операции (сложение и умножение) и три операнда. Поскольку приоритет умножения выше приоритета сложения, сначала рассчитается произведение 2 * 3, а затем — его сложение с единицей.

Позднее мы приведем полную таблицу операций с приоритетами.

Дополнительно каждый оператор характеризуется ассоциативностью. Она бывает левая и правая, и определяет, в каком порядке выполняются последовательно идущие операторы с равным приоритетом. Например, выражение 10 - 7 - 1 можно, чисто теоретически, рассчитать двумя способами:

  • сначала вычесть 7 из 10, и потом из получившейся 3-ки вычесть 1, что даст результат 2;
  • сначала вычесть 1 из 7, что даст 6, и потом вычесть 6 из 10, с результатом 4.

В первом случае вычисления проводились слева направо, что соответствует левой ассоциативности, и поскольку операция вычитания действительно левоассоциативна, то первый ответ — правильный.

Второй порядок вычислений соответствует правой ассоциативности, и не будет применен.

Рассмотрим еще один пример, в котором одновременно задействованы приоритет и ассоциативность: 11 + 5 * 4 / 2 + 3. Оба типа операций — суммирование и умножение — выполняются слева направо. Если бы не разный приоритет, мы получили бы 35, хотя правильный ответ 24. А смена ассоциативности на правую дала бы 14.

Для явного переопределения приоритетов в выражениях можно применять круглые скобки, например: (11 + 5) * 4 / (2 + 3). То, что стоит в скобках, вычисляется раньше, и промежуточный результат подставляется в выражение для участия в остальных операциях. Группы в скобках могут быть вложенными. Подробнее об этом в разделе Группировка с помощью круглых скобок.

В качестве примера оператора с правой ассоциативности можно привести унарный оператор логического отрицания '!'. Суть его работы: делать из truefalse, и наоборот. Как и для других унарных операторов, ассоциативность в этом случае означает, с какой стороны от оператора должен стоять операнд. Символ '!' ставится перед операндом, то есть операнд находится справа.

int x = 10;
int on_off = !!x;  // 1

В данном случае логическое отрицание делается дважды: первый раз по отношению к переменной x (правый '!'), а второй раз — по отношению к результату предыдущего отрицания (левый '!'). Такое двойное отрицание позволяет преобразовать любое ненулевое значение в 1, благодаря конвертации в bool и обратно.

В итоговой таблице операций будет также указана и ассоциативность.

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

Существует ограниченный набор операций, для которых порядок вычисления операндов определен, в частности, для логических И ('&&') и ИЛИ ('||') он — "слева направо", причем правая часть может быть опущена, если ничего не решает из-за значения левой части. А вот для тернарного условного оператора '?:' порядок и вовсе более "хитрый", потому что после вычисления первого условия, в зависимости от его истинности, будет вычислена либо одна, либо другая ветка. Подробнее об этом — в последующих разделах.

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

a() + b() * c() - d()

Правила приоритетов и ассоциативности будут применены только к промежуточным результатам вызовов этих функций, а сами вызовы могут быть сгенерированы компилятором в любом порядке, который он "сочтет нужным", исходя из особенностей исходного кода и настроек компилятора. Например, функции b и c, задействованные в произведении, могут вызываться в последовательности [b(), c()] или наоборот [c(), b()]. Если функции в процессе своего выполнения могут влиять на одни и те же данные, их состояние после вычисления выражения будет неоднозначным.

Аналогичную проблему можно наблюдать при работе с массивами и операторами инкремента (см. Инкремент и декремент).

int i = 0;
int a[5] = {01234};
int w = a[++i] - a[++i];

В зависимости от того, левый или правый операнд разности будет рассчитан первым, мы можем получить -1 (a[1] - a[2]) или +1 (a[2] - a[1]). Поскольку компилятор MQL5 постоянно совершенствуется, нет гарантии, что текущий результат (-1) сохранится в будущем.

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

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