Español Português
preview
От начального до среднего уровня: Перегрузка операторов (III)

От начального до среднего уровня: Перегрузка операторов (III)

MetaTrader 5Примеры |
36 0
CODE X
CODE X

Введение

В предыдущей статье От базового до среднего уровня: Перегрузка операторов (II) мы рассмотрели, как можно создать механизм отладки, а также риски, связанные с отладкой кода, в котором используется перегрузка операторов. Хотя та статья поначалу может показаться запутанной и сложной, вам, мой дорогой читатель, нужно хорошо понять, как работает этот механизм. Возможно, в какой-то момент у вас возникнут трудности с отладкой кода, в котором используется перегрузка операторов. А без этих знаний вы не сможете правильно решить некоторые довольно сложные задачи.

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

Итак, осталось ещё объяснить, как перегружать логические операторы или, как многие их называют, операторы отношения (сравнения); именно этому будет посвящена данная статья. Поэтому пришло время отложить всё, что может вас отвлекать, и сосредоточиться на самом главном. Для этого мы начнём новую тему.


Перегрузка операторов (III)

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

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct stComplex
05. {
06. //+----------------+
07.     private :
08.         double m_r, m_i;
09. //+----------------+
10.     public  :
11. //+----------------+
12.         stComplex(): m_r(0), m_i(0) {}
13. //+----------------+
14.         stComplex(double r, double i): m_r(r), m_i(i) {}
15. //+----------------+
16.         stComplex operator+(const stComplex &arg)
17.         {
18.             return stComplex(m_r + arg.m_r, m_i + arg.m_i);
19.         }
20. //+----------------+
21.         stComplex operator+(const double arg)
22.         {
23.             return stComplex(m_r + arg, m_i);
24.         }
25. //+----------------+
26.         stComplex operator+=(const double arg)
27.         {
28.             return stComplex(m_r += arg, m_i);
29.         }
30. //+----------------+
31.         stComplex Debug(uint arg)
32.         {
33.             PrintFormat("Debugging the line %d = %.02f %c %.02fi", arg, m_r, (m_i < 0 ? '-' : '+'), MathAbs(m_i));
34.             return this;
35.         }
36. //+----------------+
37. };
38. //+------------------------------------------------------------------+
39. void OnStart(void)
40. {
41.     stComplex   a(2, 5),
42.                 b(8, -3),
43.                 c;
44. 
45.     c = (a + b).Debug(__LINE__);
46.     c.Debug(__LINE__);
47.     (c += 4).Debug(__LINE__);
48.     c = (b + 4).Debug(__LINE__);
49.     c.Debug(__LINE__);
50. }
51. //+------------------------------------------------------------------+

Код 01

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

                   .
                   .
                   .
38. //+------------------------------------------------------------------+
39. void OnStart(void)
40. {
41.     for (stComplex c(1, 0), step(1, 3), max(5, 6); c < max; c += step)
42.         c.Debug(__LINE__);
43. }
44. //+------------------------------------------------------------------+

Фрагмент 01

Да, я знаю, эта конструкция кажется настоящим безумием, ведь вы ещё никогда не видели в MQL5 подобного цикла. Однако это не означает, что мы не можем создать цикл for в той форме, которая показана в этом фрагменте. Как ни странно, многие из вас сейчас, вероятно, сомневаются в моей вменяемости. Но по мере реализации каждой части вы увидите, что в MQL5 действительно можно реализовать весьма разнообразное поведение. Нам нужно лишь знать нужные концепции и правильно их применять. Хорошо, если мы изменим процедуру OnStart в соответствии с фрагментом 01, компилятор выдаст следующие сообщения.

Рисунок 01

Ну, этого и следовало ожидать. Причина проста: компилятор не знает, как использовать оператор «меньше чем», когда один из операндов является переменной типа stComplex, поскольку в коде ещё не определена необходимая перегрузка. Но что насчёт двух других ошибок? Итак, мой дорогой читатель, эти две другие ошибки на самом деле являются одной и той же ошибкой. Если вы посмотрите на код 01, то увидите, что stComplex определяет перегрузку оператора +=, правый операнд которого должен содержать значение типа double. Пока не существует другой перегрузки, которая принимала бы переменную типа stComplex в качестве правого операнда. Вы можете убедиться в этом в строке 26 кода 01.

Итак, начнём с исправления этой первой ошибки, связанной с оператором +=. Мы сохраним перегрузку, которая прибавляет значение типа double к объекту stComplex, расположенному слева от оператора, и реализуем ещё одну, которая будет складывать содержимое второй переменной stComplex. Для этого мы реализуем в коде вторую перегрузку оператора +=.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct stComplex
05. {
06. //+----------------+
07.     private :
08.         double m_r, m_i;
09. //+----------------+
10.     public  :
11. //+----------------+
12.         stComplex(): m_r(0), m_i(0) {}
13. //+----------------+
14.         stComplex(double r, double i): m_r(r), m_i(i) {}
15. //+----------------+
16.         stComplex operator+(const stComplex &arg)
17.         {
18.             return stComplex(m_r + arg.m_r, m_i + arg.m_i);
19.         }
20. //+----------------+
21.         stComplex operator+(const double arg)
22.         {
23.             return stComplex(m_r + arg, m_i);
24.         }
25. //+----------------+
26.         stComplex operator+=(const double arg)
27.         {
28.             return stComplex(m_r += arg, m_i);
29.         }
30. //+----------------+
31.         stComplex operator+=(const stComplex &arg)
32.         {
33.             return stComplex(m_r += arg.m_r, m_i += arg.m_i);
34.         }
35. //+----------------+
36.         stComplex Debug(uint arg)
37.         {
38.             PrintFormat("Debugging the line %d = %.02f %c %.02fi", arg, m_r, (m_i < 0 ? '-' : '+'), MathAbs(m_i));
39.             return this;
40.         }
41. //+----------------+
42. };
43. //+------------------------------------------------------------------+
44. void OnStart(void)
45. {
46.     for (stComplex c(1, 0), step(1, 3), max(5, 6); c < max; c += step)
47.         c.Debug(__LINE__);
48. }
49. //+------------------------------------------------------------------+

Код 02

Теперь, если мы попытаемся снова скомпилировать код, компилятор выдаст следующее сообщение об ошибке.

Рисунок 02

Отлично. Прежде чем проверять работу цикла в строке 46 кода 02, мы проведём предварительную проверку. Не слишком разумно проверять перегрузку оператора сравнения или логического оператора непосредственно в цикле. Для такой проверки лучше использовать оператор if. Поэтому мы немного изменим код 02, чтобы выполнить эту проверку. Таким образом, в код 02 будет добавлен проверочный оператор if.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct stComplex
05. {
06. //+----------------+
07.     private :
08.         double m_r, m_i;
09. //+----------------+
10.     public  :
11. //+----------------+
12.         stComplex(): m_r(0), m_i(0) {}
13. //+----------------+
14.         stComplex(double r, double i): m_r(r), m_i(i) {}
15. //+----------------+
16.         stComplex operator+(const stComplex &arg)
17.         {
18.             return stComplex(m_r + arg.m_r, m_i + arg.m_i);
19.         }
20. //+----------------+
21.         stComplex operator+(const double arg)
22.         {
23.             return stComplex(m_r + arg, m_i);
24.         }
25. //+----------------+
26.         stComplex operator+=(const double arg)
27.         {
28.             return stComplex(m_r += arg, m_i);
29.         }
30. //+----------------+
31.         stComplex operator+=(const stComplex &arg)
32.         {
33.             return stComplex(m_r += arg.m_r, m_i += arg.m_i);
34.         }
35. //+----------------+
36.         bool operator<(const stComplex &arg)
37.         {
38.             return ((m_r <= arg.m_r) && (m_i < arg.m_i));
39.         }
40. //+----------------+
41.         stComplex Debug(uint arg)
42.         {
43.             PrintFormat("Debugging the line %d = %.02f %c %.02fi", arg, m_r, (m_i < 0 ? '-' : '+'), MathAbs(m_i));
44.             return this;
45.         }
46. //+----------------+
47. };
48. //+------------------------------------------------------------------+
49. void OnStart(void)
50. {
51.     stComplex   a(1, 3),
52.                 b(1, 4);
53. 
54.     if (a < b) Print("Condition is: True"); else Print("Condition is: False.");
55.     // for (stComplex c(1, 0), step(1, 3), max(5, 6); c < max; c += step)
56.     //     c.Debug(__LINE__);
57. }
58. //+------------------------------------------------------------------+

Код 03

Обратите внимание, что я закомментировал строки цикла for и вставил оператор if, чтобы проверить, правильно ли в коде реализован оператор &lt;. При выполнении этого кода 03 терминал MetaTrader 5 покажет следующий результат.

Рисунок 03

Отлично, работает. Теперь мы можем раскомментировать строки, относящиеся к циклу for, и проверить, правильно ли он работает. Однако, поскольку меры предосторожности никогда не бывают лишними, мы добавим проверку безопасности перед тем, как протестировать этот цикл for. Цикл может стать бесконечным, поскольку мы пока не знаем наверняка, как будет вести себя код с этими перегруженными операторами. Таким образом, мы получаем версию кода с дополнительной защитой.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct stComplex
05. {
06. //+----------------+
07.     private :
08.         double m_r, m_i;
09. //+----------------+
10.     public  :
11. //+----------------+
12.         stComplex(): m_r(0), m_i(0) {}
13. //+----------------+
14.         stComplex(double r, double i): m_r(r), m_i(i) {}
15. //+----------------+
16.         stComplex operator+(const stComplex &arg)
17.         {
18.             return stComplex(m_r + arg.m_r, m_i + arg.m_i);
19.         }
20. //+----------------+
21.         stComplex operator+(const double arg)
22.         {
23.             return stComplex(m_r + arg, m_i);
24.         }
25. //+----------------+
26.         stComplex operator+=(const double arg)
27.         {
28.             return stComplex(m_r += arg, m_i);
29.         }
30. //+----------------+
31.         stComplex operator+=(const stComplex &arg)
32.         {
33.             return stComplex(m_r += arg.m_r, m_i += arg.m_i);
34.         }
35. //+----------------+
36.         bool operator<(const stComplex &arg)
37.         {
38.             return ((m_r <= arg.m_r) && (m_i < arg.m_i));
39.         }
40. //+----------------+
41.         stComplex Debug(uint arg)
42.         {
43.             PrintFormat("Debugging the line %d = %.02f %c %.02fi", arg, m_r, (m_i < 0 ? '-' : '+'), MathAbs(m_i));
44.             return this;
45.         }
46. //+----------------+
47. };
48. //+------------------------------------------------------------------+
49. void OnStart(void)
50. {
51.     stComplex   a(1, 3),
52.                 b(1, 3),
53.                 m(5, 6);
54.     int counter = 0;
55. 
56.     while (a < m)
57.     {
58.         a.Debug(__LINE__);
59.         a += b;
60.         counter++;
61.         if (counter > 50) break;
62.     }
63. 
64.     a.Debug(__LINE__);
65.     Print("The counter value is: ", counter);
66. 
67.     // for (stComplex c(1, 0), step(1, 3), max(5, 6); c < max; c += step)
68.     //     c.Debug(__LINE__);
69. }
70. //+------------------------------------------------------------------+

Код 04

Теперь проанализируйте изменения в коде. При его выполнении мы получим неверное значение счётчика.

Рисунок 04

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

Хорошо, но где же проблема в коде? А это уже простая часть, мой дорогой читатель. Проблема в строке 38. Обратите внимание на следующее: когда мы выполняли проверку с помощью условного оператора if, именно строка 38 позволяла корректно выполнить эту проверку. Однако при использовании цикла while проверка перестала работать должным образом. Значение m_i приводит к тому, что выражение возвращает false ещё до того, как m_i достигнет или превысит значение m_r. Видите, как просто найти ошибку? Чтобы исправить это, нам нужно заменить строку 38 в коде 04 выражением из фрагмента 02.

                   .
                   .
                   .
38.            return ((m_r < arg.m_r) ? true : (m_r > arg.m_r ? false : (m_i < arg.m_i)));;
                   .
                   .
                   .

Фрагмент 02

При повторном выполнении код выдаёт следующий результат:

Рисунок 05

Отлично. Всё сработало отлично. Теперь, наконец, мы можем раскомментировать строки цикла for и повторно использовать реализацию из фрагмента 01. Если теперь запустить код с циклом for, мы получим правильный результат подсчёта.

Рисунок 06

Просто и очень забавно, правда, мой дорогой читатель? Очень хорошо, это была лёгкая часть. Теперь мы расширим реализацию и в то же время сделаем её гораздо интереснее. Для этого мы реализуем оператор &gt; с помощью ещё одной перегрузки.

                   .
                   .
                   .
40. //+----------------+
41.         bool operator>(const stComplex &arg)
42.         {
43.             return ((m_r > arg.m_r) ? true : (m_r < arg.m_r ? false : (m_i > arg.m_i)));
44.         }
45. //+----------------+
                   .
                   .
                   .

Фрагмент 03

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

Полный код 05 послужит основой для последующих изменений.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct stComplex
05. {
06. //+----------------+
07.     private :
08.         double m_r, m_i;
09. //+----------------+
10.     public  :
11. //+----------------+
12.         stComplex(): m_r(0), m_i(0) {}
13. //+----------------+
14.         stComplex(double r, double i): m_r(r), m_i(i) {}
15. //+----------------+
16.         stComplex operator+(const stComplex &arg)
17.         {
18.             return stComplex(m_r + arg.m_r, m_i + arg.m_i);
19.         }
20. //+----------------+
21.         stComplex operator+(const double arg)
22.         {
23.             return stComplex(m_r + arg, m_i);
24.         }
25. //+----------------+
26.         stComplex operator+=(const double arg)
27.         {
28.             return stComplex(m_r += arg, m_i);
29.         }
30. //+----------------+
31.         stComplex operator+=(const stComplex &arg)
32.         {
33.             return stComplex(m_r += arg.m_r, m_i += arg.m_i);
34.         }
35. //+----------------+
36.         bool operator<(const stComplex &arg)
37.         {
38.             return ((m_r < arg.m_r) ? true : (m_r > arg.m_r ? false : (m_i < arg.m_i)));
39.         }
40. //+----------------+
41.         bool operator>(const stComplex &arg)
42.         {
43.             return ((m_r > arg.m_r) ? true : (m_r < arg.m_r ? false : (m_i > arg.m_i)));
44.         }
45. //+----------------+
46.         stComplex Debug(uint arg)
47.         {
48.             PrintFormat("Debugging the line %d = %.02f %c %.02fi", arg, m_r, (m_i < 0 ? '-' : '+'), MathAbs(m_i));
49.             return this;
50.         }
51. //+----------------+
52. };
53. //+------------------------------------------------------------------+
54. void OnStart(void)
55. {
56.     stComplex   a(1, 3);
57.     int counter = 0;
58. 
59.     while (a < stComplex(5, 6))
60.     {
61.         a.Debug(__LINE__);
62.         a += stComplex(1, 3);
63.         counter++;
64.         if (counter > 50) break;
65.     }
66. 
67.     a.Debug(__LINE__);
68.     Print("The counter value is: ", counter);
69. }
70. //+------------------------------------------------------------------+

Код 05

Теперь я хочу, чтобы вы были очень внимательны, потому что все изменения, которые мы будем вносить, начиная с этого места, будут связаны с этим кодом 05. Возможно, вы думаете: «Ну надо же, ты совсем сошёл с ума. Я почти ничего не понимаю в этом коде 05». Однако реализация кода 05 — это не что иное, как эквивалентная переработка кода 04. Но я хочу обратить ваше внимание на строки 59 и 62.

В коде 05 у нас уже есть всё необходимое, чтобы изменить работу строки 62. Это мы уже объясняли и применяли в предыдущих статьях. Однако пока мы не можем таким же образом изменить условие в строке 59. Пока нет. Тем не менее, я хочу рассмотреть некоторые возможности, которые даёт эта реализация. Сначала мы выполним этот код как есть. В результате выполнения будет получен результат, показанный на рисунке 07.

Рисунок 07

Хорошо. Теперь мы перепишем выражение в строке 59.

while (stComplex(5, 6) > a)

Код по-прежнему будет давать результат, показанный на рисунке 07. Теперь мы перепишем операцию в строке 62.

a += 1;

В этом случае выполнение даст другой результат.

Рисунок 08

Обратите внимание, что в качестве правого операнда мы можем без каких-либо проблем использовать как целое число, так и переменную stComplex. А что насчёт условия завершения цикла? Можно ли записать и это условие при любом порядке операндов? Хорошо, проверим эту возможность прямо в коде. Поэтому теперь мы поменяем порядок операндов в условии в строке 59.

while (a < 5)

При попытке скомпилировать код с этим изменением компилятор выдаст следующие сообщения об ошибках.

Рисунок 09

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct stComplex
05. {
06. //+----------------+
07.     private :
08.         double m_r, m_i;
09. //+----------------+
10.     public  :
11. //+----------------+
12.         stComplex(): m_r(0), m_i(0) {}
13. //+----------------+
14.         stComplex(double r, double i): m_r(r), m_i(i) {}
15. //+----------------+
16.         stComplex operator+(const double arg)
17.         {
18.             return stComplex(m_r + arg, m_i);
19.         }
20. //+----------------+
21.         stComplex operator+=(const double arg)
22.         {
23.             return stComplex(m_r += arg, m_i);
24.         }
25. //+----------------+
26.         bool operator<(const double arg)
27.         {
28.             return (m_r < arg);
29.         }
30. //+----------------+
31.         bool operator>(const double arg)
32.         {
33.             return (m_r > arg);
34.         }
35. //+----------------+
36.         stComplex operator+(const stComplex &arg)
37.         {
38.             return stComplex(m_r + arg.m_r, m_i + arg.m_i);
39.         }
40. //+----------------+
41.         stComplex operator+=(const stComplex &arg)
42.         {
43.             return stComplex(m_r += arg.m_r, m_i += arg.m_i);
44.         }
45. //+----------------+
46.         bool operator<(const stComplex &arg)
47.         {
48.             return ((m_r < arg.m_r) ? true : (m_r > arg.m_r ? false : (m_i < arg.m_i)));
49.         }
50. //+----------------+
51.         bool operator>(const stComplex &arg)
52.         {
53.             return ((m_r > arg.m_r) ? true : (m_r < arg.m_r ? false : (m_i > arg.m_i)));
54.         }
55. //+----------------+
56.         stComplex Debug(uint arg)
57.         {
58.             PrintFormat("Debugging the line %d = %.02f %c %.02fi", arg, m_r, (m_i < 0 ? '-' : '+'), MathAbs(m_i));
59.             return this;
60.         }
61. //+----------------+
62. };
63. //+------------------------------------------------------------------+
64. void OnStart(void)
65. {
66.     stComplex   a(1, 3);
67.     int counter = 0;
68. 
69.     while (a < 5)
70.     {
71.         a.Debug(__LINE__);
72.         a += 1;
73.         counter++;
74.         if (counter > 50) break;
75.     }
76. 
77.     a.Debug(__LINE__);
78.     Print("The counter value is: ", counter);
79. }
80. //+------------------------------------------------------------------+

Код 06

Теперь компилятор корректно компилирует этот новый код и создаёт исполняемый файл.

Рисунок 10

Теперь возникает ограничение, которое MQL5 НЕ ДОПУСКАЕТ. Фактически, насколько мне известно, только язык SMALLTALK допускает такую конструкцию, потому что в нём всё является объектом. Буквально это язык, который довёл объектно-ориентированное программирование до крайности. Кроме него, я не знаю ни одного другого языка, который допускает такую конструкцию. Хотя языков программирования существует так много, что нельзя исключать наличие и других, поддерживающих такую конструкцию. И это несмотря на то, что на протяжении многих лет я изучал различные языки программирования. Да, мой дорогой читатель, я могу читать и понимать код, написанный на разных языках программирования. Однако писать на многих из них я не умею, а только на нескольких. Но вернёмся к порядку операндов. Условие во фрагменте 04 показывает, в чём состоит проблема.

                   .
                   .
                   .
63. //+------------------------------------------------------------------+
64. void OnStart(void)
65. {
66.     stComplex   a(1, 3);
67.     int counter = 0;
68. 
69.     while (5 > a)
70.     {
71.         a.Debug(__LINE__);
72.         a += 1;
73.         counter++;
74.         if (counter > 50) break;
75.     }
76. 
77.     a.Debug(__LINE__);
78.     Print("The counter value is: ", counter);
79. }
80. //+------------------------------------------------------------------+

Фрагмент 04

Теперь, если вы попытаетесь скомпилировать код 06 после замены условия на условие из фрагмента 04, компилятор выдаст соответствующую ошибку.

Рисунок 11

Почему компилятор выдаёт эту ошибку? В принципе, stComplex определяет перегрузку оператора >, которая допускает в качестве правого операнда значение встроенного числового типа — либо целое, либо с плавающей точкой. Это определение содержится в строке 31 кода 06.

Причина этой ошибки в том, что вы, возможно, забыли, как компилятор интерпретирует перегруженные операторы. Проблема не в том, реализует ли код этот оператор, а в том, допускает ли компилятор такой порядок операндов. Многие, возможно, подумают: «Конечно, это не сработает; литерал 5 — не переменная. Поэтому компилятор не сможет определить, как выполнить сравнение». Однако причина не в этом, хотя такое рассуждение довольно близко к истине. Чтобы это продемонстрировать, заменим литерал переменной.

                   .
                   .
                   .
63. //+------------------------------------------------------------------+
64. void OnStart(void)
65. {
66.     stComplex   a(1, 3);
67.     int counter = 0;
68. 
69.     while (counter > a)
70.     {
71.         a.Debug(__LINE__);
72.         a += 1;
73.         counter++;
74.         if (counter > 50) break;
75.     }
76. 
77.     a.Debug(__LINE__);
78.     Print("The counter value is: ", counter);
79. }
80. //+------------------------------------------------------------------+

Фрагмент 05

Даже в этом случае компилятор по-прежнему будет выдавать ошибку того же типа, что доказывает, что проблема не в использовании литерала или переменной. Проблема в типе левого операнда: перегрузка относится к stComplex, и компилятор не может вызвать её, когда в левом операнде стоит значение встроенного типа. Мы должны сформировать значение stComplex из этого левого операнда. Таким образом, компилятор правильно интерпретирует код и сгенерирует исполняемый файл.

В любом случае мы должны применить это преобразование одним и тем же способом как во фрагменте 04, так и во фрагменте 05. Если взять в качестве примера фрагмент 04, нам нужно явно преобразовать левый операнд к типу stComplex.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct stComplex
05. {
06. //+----------------+
07.     private :
08.         double m_r, m_i;
09. //+----------------+
10.     public  :
11. //+----------------+
12.         stComplex(): m_r(0), m_i(0) {}
13. //+----------------+
14.         stComplex(double r): m_r(r), m_i(0) {}
15. //+----------------+
16.         stComplex(double r, double i): m_r(r), m_i(i) {}
17. //+----------------+
                   .
                   .
                   .
63. //+----------------+
64. };
65. //+------------------------------------------------------------------+
66. void OnStart(void)
67. {
68.     stComplex   a(1, 3);
69.     int counter = 0;
70. 
71.     while (stComplex(5) > a)
72.     {
73.         a.Debug(__LINE__);
74.         a += 1;
75.         counter++;
76.         if (counter > 50) break;
77.     }
78. 
79.     a.Debug(__LINE__);
80.     Print("The counter value is: ", counter);
81. }
82. //+------------------------------------------------------------------+

Фрагмент 06

Теперь компилятор сгенерирует исполняемый файл. Однако для этого нам пришлось объявить в структуре новый конструктор. Это объявление содержится в строке 14. Без его реализации компилятор выдаст соответствующую ошибку.

Рисунок 12

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

Рисунок 13

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

Итак, давайте сразу же рассмотрим ещё два оператора. Оба они реализованы в коде 07.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. struct stComplex
005. {
006. //+----------------+
007.     private :
008.         double m_r, m_i;
009. //+----------------+
010.     public  :
011. //+----------------+
012.         stComplex(): m_r(0), m_i(0) {}
013. //+----------------+
014.         stComplex(double r): m_r(r), m_i(0) {}
015. //+----------------+
016.         stComplex(double r, double i): m_r(r), m_i(i) {}
017. //+----------------+
018.         stComplex operator+(const double arg)
019.         {
020.             return stComplex(m_r + arg, m_i);
021.         }
022. //+----------------+
023.         stComplex operator+=(const double arg)
024.         {
025.             return stComplex(m_r += arg, m_i);
026.         }
027. //+----------------+
028.         bool operator<(const double arg)
029.         {
030.             return (m_r < arg);
031.         }
032. //+----------------+
033.         bool operator>(const double arg)
034.         {
035.             return (m_r > arg);
036.         }
037. //+----------------+
038.         stComplex operator+(const stComplex &arg)
039.         {
040.             return stComplex(m_r + arg.m_r, m_i + arg.m_i);
041.         }
042. //+----------------+
043.         stComplex operator+=(const stComplex &arg)
044.         {
045.             return stComplex(m_r += arg.m_r, m_i += arg.m_i);
046.         }
047. //+----------------+
048.         bool operator<(const stComplex &arg)
049.         {
050.             return ((m_r < arg.m_r) ? true : (m_r > arg.m_r ? false : (m_i < arg.m_i)));
051.         }
052. //+----------------+
053.         bool operator>(const stComplex &arg)
054.         {
055.             return ((m_r > arg.m_r) ? true : (m_r < arg.m_r ? false : (m_i > arg.m_i)));
056.         }
057. //+----------------+
058.         bool operator&&(const stComplex &arg)
059.         {
060.             return m_r && arg.m_r && m_i && arg.m_i;
061.         }
062. //+----------------+
063.         stComplex operator&(const stComplex &arg)
064.         {
065.             union u1
066.             {
067.                 double p64;
068.                 ulong  u64;
069.             }v_r0, v_i0, v_r1, v_i1;
070. 
071.             v_r0.p64 = m_r;
072.             v_i0.p64 = m_i;
073.             v_r1.p64 = arg.m_r;
074.             v_i1.p64 = arg.m_i;
075. 
076.             v_r0.u64 &= v_r1.u64;
077.             v_i0.u64 &= v_i1.u64;
078. 
079.             return stComplex(v_r0.p64, v_i0.p64);
080.         }
081. //+----------------+
082.         stComplex Debug(uint arg)
083.         {
084.             PrintFormat("Debugging the line %d = %.02f %c %.02fi", arg, m_r, (m_i < 0 ? '-' : '+'), MathAbs(m_i));
085.             return this;
086.         }
087. //+----------------+
088. };
089. //+------------------------------------------------------------------+
090. void OnStart(void)
091. {
092.     stComplex   a(15, 49),
093.                 b(7, 25);
094. 
095.     a.Debug(__LINE__);
096.     b.Debug(__LINE__);
097. 
098.     Print("The result of A & B is:");
099.     (a & b).Debug(__LINE__);
100. 
101.     Print("The result of A && B is: ", (a && b ? "TRUE" : "FALSE"));
102. }
103. //+------------------------------------------------------------------+

Код 07

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

Рисунок 14

Этот результат может показаться не вполне интуитивным, если вы не понимаете, как взаимодействуют двоичное представление и побитовые операции. Поскольку я уже объяснял и показывал в предыдущих статьях этой же серии всё, что используется в коде 07, я не буду повторять эти объяснения. Если у вас возникнут сомнения, обратитесь к предыдущим статьям за более подробной информацией. Однако ни в одной из этих статей не объясняется, как двоичное представление значений типа double определяет результат, показанный на рисунке 14.

Чтобы понять, как двоичное представление определяет результат, вам необходимо освоить то, что я объяснил в другой статье, которой, возможно, многие не придали того значения, которого она заслуживает. Я имею в виду статью «От базового к среднему: плавающая точка». Там я объясняю, как интерпретировать значение с плавающей точкой по составляющим его битам; это знание позволяет понять результат, выводимый в строке 99 кода 07.

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

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. struct stComplex
005. {
                   .
                   .
                   .
081. //+----------------+
082.         string ToString(void)
083.         {
084.             return StringFormat("%.02f %c %.02fi", m_r, (m_i < 0 ? '-' : '+'), MathAbs(m_i));
085.         }
086. //+----------------+
087.         stComplex Debug(uint arg)
088.         {
089.             Print("Debugging the line ", arg, " = ", this.ToString());
090.             return this;
091.         }
092. //+----------------+
093. };
094. //+------------------------------------------------------------------+
095. void OnStart(void)
096. {
097.     stComplex   a(15, 49),
098.                 b(7, 25);
099.     Print("Variable A: ", a.ToString());
100.     Print("Variable B: ", b.ToString());
101.     Print("The result of A & B is: ", (a & b).ToString());
102.     Print("The result of A && B is: ", (a && b ? "TRUE" : "FALSE"));
103. }
104. //+------------------------------------------------------------------+

Фрагмент 07

Теперь при выполнении кода мы получим более ясный вывод.

Рисунок 15

Теперь это действительно гораздо проще понять. Но вернёмся к побитовой операции. Почему A & B даёт такой результат? Это не имеет никакого смысла. Что ж, возможно, для вас, мой дорогой читатель, это не так, если вы сразу перешли к этой статье. Но для тех, кто изучал статьи этой серии, результат имеет вполне понятный смысл. Компилятор применяет побитовую операцию не непосредственно к значениям с плавающей точкой, а к их целочисленным представлениям. Что? Подождите минутку. Теперь уже я, следивший за статьями, запутался. Как это операнды этой операции — целые числа? Разве в строке 8 кода 07 не объявлено, что структура хранит значения типа double?

Да, мой дорогой читатель, эта структура хранит значения типа double, то есть значения с плавающей точкой двойной точности. Однако компилятор НЕ может применять побитовую операцию непосредственно к этим значениям. Перед выполнением операции мы должны предоставить 64-битные целые операнды, представляющие то же двоичное содержимое. Вот почему вы получаете результат, показанный на рисунках 14 и 15.

Я всё ещё этого не понимаю. Не могли бы вы объяснить это получше? Без проблем, мой дорогой читатель. Чтобы окончательно это понять, мы создадим небольшой код, который позволит воспроизвести операцию шаг за шагом.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     double  v1 = 15,
07.             v2 = 7,
08.             v3;
09. 
10.     v3 = v1 & v2;
11. 
12.     Print(v3);
13. }
14. //+------------------------------------------------------------------+

Код 08

Когда вы попытаетесь скомпилировать этот код, компилятор покажет следующую ошибку компиляции.

Рисунок 16

Компилятор выдаёт эту ошибку, потому что НЕ ЗНАЕТ, как выполнить операцию побитового AND, когда оба операнда имеют тип double. Чтобы решить эту проблему, нам нужно преобразовать каждый операнд типа double в целое число, поскольку компилятор действительно может применять эту операцию к целочисленным операндам. Следующий код выполняет это явное числовое преобразование.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     double  v1 = 15,
07.             v2 = 7,
08.             v3;
09.     ulong   u1;
10. 
11.     u1 = ulong(v1) & ulong(v2);
12.     v3 = double(u1);
13. 
14.     Print(v3);
15. }
16. //+------------------------------------------------------------------+

Код 09

Действительно, в строках одиннадцать и двенадцать мы преобразуем значения, хранящиеся в v1 и v2, из типа double в целочисленный тип, что позволяет компилятору сгенерировать исполняемый файл. Однако я хочу, чтобы вы обратили внимание на одно конкретное совпадение: значения v1 и v2 точно такие же, как те, которые мы использовали в качестве действительной части в предыдущих кодах. Следовательно, мы должны получить результат, равный тому, который даёт перегрузка оператора побитового AND. По крайней мере, мы предполагаем, что именно это и должно произойти. Однако при выполнении этого кода 09 программа выдаёт другой результат.

Рисунок 17

Но что же здесь произошло? Почему результаты отличаются? Я объяснял причину этого в других статьях, мой дорогой читатель, где показывал, как мы можем использовать память в своих интересах. Одна из них — «От базового к среднему: Объединение (I)». В этих статьях об объединениях я объяснил и показал, как организовывать память и работать с ней без потери информации. При преобразовании значения с плавающей точкой в целое число происходит потеря информации, поскольку отбрасывается дробная часть этого значения. Именно эту деталь я не объяснил, когда рассказывал об объединениях.

"Что ж, теперь я начинаю понимать причину такого результата. Но существует ли более простой способ проверить двоичное представление значений типа double? Постоянно повторять те деления и умножения, которые вы показывали, объясняя, как кодировать десятичное число в формате с плавающей точкой, очень утомительно и изнурительно". Мы можем напрямую проверить биты, в которых хранится значение типа double, не преобразуя его в целое число. Единственное требование — уметь интерпретировать это представление. Для этого вам нужно понять статью, в которой я объясняю модель плавающей точки. Обладая этими знаниями, мы можем изменить код 09 так, чтобы он читал те же биты как целочисленное представление, и применить к ним побитовую операцию.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     union _u
07.     {
08.         ulong   u64;
09.         double  pf64;
10.     }v1, v2, v3;
11. 
12.     v1.pf64 = 15;
13.     v2.pf64 = 7;
14.     v3.u64 = v1.u64 & v2.u64;
15. 
16.     Print(v3.pf64);
17. }
18. //+------------------------------------------------------------------+

Код 10

Итак, при выполнении изменённого кода 09 программа выдаст результат новой побитовой операции.

Рисунок 18

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     union _u
07.     {
08.         ulong   u64;
09.         double  pf64;
10.     }v1, v2, v3;
11. 
12.     #define PrintContent(x) PrintFormat("Memory content in variable %s is: %I64X", #x, x.u64)
13. 
14.     v1.pf64 = 15;
15.     v2.pf64 = 7;
16.     v3.u64 = v1.u64 & v2.u64;
17. 
18.     PrintContent(v1);
19.     PrintContent(v2);
20.     PrintContent(v3);
21.     Print("The result of v1 & v2 is: ", v3.pf64);
22. }
23. //+------------------------------------------------------------------+

Код 11

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

Рисунок 19

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

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

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


Заключительные замечания

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

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

Файл MQ5 Описание
Код 01 Базовая демонстрация
Код 02 Базовая демонстрация
Код 03 Базовая демонстрация
Код 04 Базовая демонстрация
Код 05 Базовая демонстрация

Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/16938

Прикрепленные файлы |
Anexo.zip (3.56 KB)
Причинный граф как признак машинного обучения Причинный граф как признак машинного обучения
В статье условный граф причинности Грейнджера по 10 валютным парам и трём таймфреймам преобразуется в числовые признаки для CatBoost и MQL5. Абляция сравнивает модели price-only, graph-only и price+graph: графовые признаки почти не работают самостоятельно, но в сочетании с ценой улучшают контекст, режимную фильтрацию и риск. На форварде совместная модель показала более высокий Profit Factor и меньшую просадку.
Моделирование рынка: В единстве — сила (I) Моделирование рынка: В единстве — сила (I)
Мы подходим к финишной прямой. Разработка системы репликации/моделирования почти завершена. Конечно, нам ещё предстоит доделать кое-что, но по сравнению со всем, что мы уже сделали, реализовать оставшееся будет несложно. Однако всё, что будет показано в этой статье, необходимо как следует усвоить и понять. Поэтому я желаю вам приятного чтения и, прежде всего, получить удовольствие от этого последнего этапа пути.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Алгоритм оптимизации на основе приспособленности — Fitness Dependent Optimizer (FDO) Алгоритм оптимизации на основе приспособленности — Fitness Dependent Optimizer (FDO)
Разбираем FDO — алгоритм, вдохновлённый роением пчёл, но интересный не пчёлами. Формулы просты, но направление шага фиксирует агента на луче к глобально лучшему, сводя поиск к одной степени свободы. Мы воспроизводим канонику, сопоставляем результат с равномерным случайным перебором на том же бюджете вычислений и устраняем дефект минимальной правкой. Прилагаются готовая реализация на MQL5, полный протокол тестов и методика нуль‑модели для оценки пользы итераций.