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

От начального до среднего уровня: Определения (I)

MetaTrader 5Примеры | 6 июня 2025, 10:02
205 0
CODE X
CODE X

Введение

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

В предыдущей статье "От начального до среднего уровня: Рекурсия" мы рассказали, что такое рекурсия и как можно использовать ее в качестве очень полезного приема программирования в различных сценариях. Она позволяет нам создавать механизмы и реализации простым и легким способом, хотя мы должны учитывать возможность того, что код иногда будет работать медленнее, поэтому в таких случаях следует проявлять терпение.

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

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


Каковы будут определения?

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

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

Например, в MQL5 можно импортировать код и функциональность, созданные другими программистами, обычно на языке C или C++. Это позволяет добавить в MetaTrader 5 функциональные возможности или элементы, представляющие личный интерес, такие как, например, видеоплеер, или даже разработать утилиту для создания продвинутых научных графиков, с возможностью использования языка, подобного LaTeX, который, если вы не знали является языком, позволяющим форматировать математические выражения. Очень интересно, между прочим.

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

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     const uchar who = 6;
07. 
08.     Print("Result: ", Fibonacci_Recursive(who));
09.     Print("Result: ", Fibonacci_Interactive(who));
10. }
11. //+------------------------------------------------------------------+
12. uint Fibonacci_Recursive(uint arg)
13. {
14.     if (arg <= 1)
15.         return arg;
16. 
17.     return Fibonacci_Recursive(arg - 1) +  Fibonacci_Recursive(arg - 2);
18. }
19. //+------------------------------------------------------------------+
20. uint Fibonacci_Interactive(uint arg)
21. {
22.     uint v, i, c;
23. 
24.     for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2)
25.         v += i;
26. 
27.     return (c == arg ? i : v);
28. }
29. //+------------------------------------------------------------------+

Код 01

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

Если предположить, что мы хотим использовать здесь директиву компиляции, то код 01 можно переписать так:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Fibonacci_Element   6
05. //+------------------------------------------------------------------+
06. void OnStart(void)
07. {
08.     Print("Result: ", Fibonacci_Recursive(def_Fibonacci_Element));
09.     Print("Result: ", Fibonacci_Interactive(def_Fibonacci_Element));
10. }
11. //+------------------------------------------------------------------+
                   .
                   .
                   .

Код 02

Конечно, мы не повторим весь код, так как в этом нет необходимости. Однако обратите внимание на то, как мы это реализовали. Кажется, это не имеет никакого значения, но на самом деле здесь есть разница, мой дорогой читатель. Для тех, кто читает код, он может выглядеть одинаково, но для компилятора код 01 отличается от кода 02 именно тем, что в коде 02 мы используем директиву #define.

Таким образом, для компилятора на самом деле будет виден код, который показан ниже:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     Print("Result: ", Fibonacci_Recursive(6));
07.     Print("Result: ", Fibonacci_Interactive(6));
08. }
09. //+------------------------------------------------------------------+
                   .
                   .
                   .

Код 03

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

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

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Fibonacci_Element_Default   6
05. //+------------------------------------------------------------------+
06. void OnStart(void)
07. {
08.     Print("Result: ", Fibonacci_Recursive());
09.     Print("Result: ", Fibonacci_Interactive());
10. }
11. //+------------------------------------------------------------------+
12. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default)
13. {
14.     if (arg <= 1)
15.         return arg;
16. 
17.     return Fibonacci_Recursive(arg - 1) +  Fibonacci_Recursive(arg - 2);
18. }
19. //+------------------------------------------------------------------+
20. uint Fibonacci_Interactive(uint arg = def_Fibonacci_Element_Default)
21. {
22.     uint v, i, c;
23. 
24.     for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2)
25.         v += i;
26. 
27.     return (c == arg ? i : v);
28. }
29. //+------------------------------------------------------------------+

Код 04

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Fibonacci_Element_Default   6
05. //+------------------------------------------------------------------+
06. void OnStart(void)
07. {
08.     Print("Result: ", Fibonacci_Recursive());
09.     Print("Result: ", Fibonacci_Interactive());
10. }
11. //+------------------------------------------------------------------+
12. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default)
13. {
14.     if (arg <= 1)
15.         return arg;
16. 
17.     return Fibonacci_Recursive(arg - 1) +  Fibonacci_Recursive(arg - 2);
18. }
19. //+------------------------------------------------------------------+
20. #undef def_Fibonacci_Element_Default
21. //+------------------------------------------------------------------+
22. uint Fibonacci_Interactive(uint arg = def_Fibonacci_Element_Default)
23. {
24.     uint v, i, c;
25. 
26.     for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2)
27.         v += i;
28. 
29.     return (c == arg ? i : v);
30. }
31. //+------------------------------------------------------------------+

Код 05

Обратите внимание, что в строке 20 данного кода мы используем еще одну директиву компиляции. Это и есть #undef. При использовании данной директивы мы можем удалить или уничтожить директиву, которая была определена с именем, содержащимся в директиве #undef. Такие возможности очень полезны. Однако прежде, чем мы поговорим о более насущных утилитах, нам нужно обсудить, что происходит, когда мы пытаемся скомпилировать код 05. При попытке сбора компилятор выдаст ошибку:

Рисунок 01

Он указывает, что ошибка возникла в строке 22. Однако константа def_Fibonacci_Element_Default была уничтожена в строке 20. Таким образом, когда компилятор попытается найти именованную константу в базе данных, чтобы скомпилировать код, он ее не найдет, что приведет к ошибке, которую можно видеть на изображении 01: По этой причине у многих программистов есть привычка ставить префикс к ошибке, чтобы помочь определить тип генерируемой ошибки. Это не правило, а хорошая практика программирования. Я, например, люблю прикреплять к любой определенной константе префикс def_, таким образом, я могу отличить общую константу от директивы компиляции.

"Хорошо, но что если мы хотим объявить другое значение для директивы сразу после ее уничтожения, сможем ли мы это сделать?" Конечно, мой дорогой читатель, но мы просто должны быть осторожны при этом. Потом я покажу вам, как избежать этого, и избежать лишней головной боли. Это не на 100% верное средство, но, по крайней мере, оно помогает. Однако давайте посмотрим, что произойдет, если мы изменим директиву, как предлагается.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Fibonacci_Element_Default   6
05. //+------------------------------------------------------------------+
06. void OnStart(void)
07. {
08.     Print("Result: ", Fibonacci_Recursive());
09.     Print("Result: ", Fibonacci_Interactive());
10. }
11. //+------------------------------------------------------------------+
12. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default)
13. {
14.     if (arg <= 1)
15.         return arg;
16. 
17.     return Fibonacci_Recursive(arg - 1) +  Fibonacci_Recursive(arg - 2);
18. }
19. //+------------------------------------------------------------------+
20. #undef def_Fibonacci_Element_Default
21. //+----------------+
22. #define def_Fibonacci_Element_Default   7
23. //+------------------------------------------------------------------+
24. uint Fibonacci_Interactive(uint arg = def_Fibonacci_Element_Default)
25. {
26.     uint v, i, c;
27. 
28.     for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2)
29.         v += i;
30. 
31.     return (c == arg ? i : v);
32. }
33. //+------------------------------------------------------------------+

Код 06

Итак, в коде 06 мы видим, как реализовалось предложенное. Обратите внимание, что в строке 22 мы определяем новое значение для директивы, и при выполнении данного кода результат будет таким, как показано ниже:

Рисунок 02

Если вы заметили, результаты отличаются, но этого следовало ожидать именно из-за изменения значения. Если бы директива, определенная в строке 4, была глобальной константой, мы бы не смогли внести изменение, которое показываем в строке 22, и не смогли бы удалить глобальную константу из других частей кода. Это основа, которую вы должны попытаться понять. Это не просто то, что вы должны выучить наизусть, а то, что вы должны понять и усвоить.

Хорошо, мы увидели, что директивы компиляции #define и #undef можно использовать вместе простым способом. Потому что, как я недавно упоминал, существуют более сложные способы использования этих двух директив вместе. Однако для этого нам нужно использовать другие директивы, которые упрощают работу и облегчают контроль.


Управление версией кода

Один из самых интересных способов использования данных директив #define и #undef - контроль версий одного и того же кода. Поскольку данный код для вычисления элемента в последовательности Фибоначчи очень прост для понимания, мы будем использовать его, чтобы объяснить управление версиями.

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define DEF_INTERACTIVE
05. //+----------------+
06. #define def_Fibonacci_Element_Default   6
07. //+------------------------------------------------------------------+
08. void OnStart(void)
09. {
10.     Print("Result: ", Fibonacci());
11. }
12. //+------------------------------------------------------------------+
13. #ifndef DEF_INTERACTIVE
14. //+----------------+
15. uint Fibonacci(uint arg = def_Fibonacci_Element_Default)
16. {
17.     if (arg <= 1)
18.         return arg;
19. 
20.     return Fibonacci(arg - 1) +  Fibonacci(arg - 2);
21. }
22. //+----------------+
23. #endif
24. //+------------------------------------------------------------------+
25. #ifdef DEF_INTERACTIVE
26. //+----------------+
27. uint Fibonacci(uint arg = def_Fibonacci_Element_Default)
28. {
29.     uint v, i, c;
30. 
31.     for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2)
32.         v += i;
33. 
34.     return (c == arg ? i : v);
35. }
36. //+----------------+
37. #endif
38. //+------------------------------------------------------------------+

Код 07

Здесь мы показываем первую альтернативу, чтобы посмотреть, что мы можем сделать. Обратите внимание, что в строке 4 кода 07 мы создаем определение. Оно не обязательно должно содержать какое-либо значение, оно просто должно существовать, или, скорее, должно быть возможным для компилятора, чтобы его увидеть. Теперь обратите внимание, что в строке 10 у нас только одна функция Фибоначчи. Но я спрашиваю вас: какая из них будет использоваться - та, что в строке 15, или та, что в строке 27? На данном этапе вы можете сказать: это бессмысленно. Разве могут быть две функции с одинаковым именем? Да, могут, но об этом мы поговорим в другой раз. Пока что остановимся на том, что показано в коде 07.

Если вы никогда не видели подобной конструкции, вам будет крайне тяжело понять, что здесь происходит. Дело в том, что в этом нет никакого смысла на первый взгляд. Но посмотрите внимательно на код. Обратите внимание, что в строке 13 мы используем еще одну директиву компиляции. При этом проверяется, существует ли объявленная директива или нет, и код между данными директивами #ifndef и #endif будет скомпилирован только в том случае, если директива НЕ СУЩЕСТВУЕТ. В противном случае код не компилируется. Нечто подобное происходит в строке 25, где код компилируется только тогда, когда директива СУЩЕСТВУЕТ. То есть, поскольку строка 4 определяет директиву, в которой мы хотим использовать код, компилироваться будет только код между строками 25 и 37. Тем временем код между строками 13 и 23 игнорируется.

Не так ли? Давайте проверим это на практике. Чтобы это стало действительно приемлемым, давайте добавим небольшую строчку в код 07, таким образом он будет так, как показано ниже:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define DEF_INTERACTIVE 
05. //+----------------+
06. #define def_Fibonacci_Element_Default   6
07. //+------------------------------------------------------------------+
08. void OnStart(void)
09. {
10.     Print("Result: ", Fibonacci());
11. }
12. //+------------------------------------------------------------------+
13. #ifndef DEF_INTERACTIVE
14. //+----------------+
15. uint Fibonacci(uint arg = def_Fibonacci_Element_Default)
16. {
17.     Print("Testing ", __LINE__);
18.     if (arg <= 1)
19.         return arg;
20. 
21.     return Fibonacci(arg - 1) +  Fibonacci(arg - 2);
22. }
23. //+----------------+
24. #endif
25. //+------------------------------------------------------------------+
26. #ifdef DEF_INTERACTIVE
27. //+----------------+
28. uint Fibonacci(uint arg = def_Fibonacci_Element_Default)
29. {
30.     uint v, i, c;
31. 
32.     Print("Testing ", __LINE__);
33.     for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2)
34.         v += i;
35. 
36.     return (c == arg ? i : v);
37. }
38. //+----------------+
39. #endif
40. //+------------------------------------------------------------------+

Код 08

Запустив код 08, мы увидим следующее:

Рисунок 03

Теперь изменим строку 4 на то, что показано ниже:

// #define DEF_INTERACTIVE 

Далее скомпилируем код 08 еще раз, и результат будет таким:

Рисунок 04

И это всё! Доказано: это работает. В одной версии мы используем рекурсивные вычисления, в другой - итеративные. А чтобы выбрать, какую версию нам использовать, достаточно изменить одну строчку кода. Разве не интересна такая реализация и использование директив в нашем коде? Но речь не только об этом. Мы можем сделать кое-что еще более интересное. Обратите внимание, что показанный код может быть восстановлен гораздо более простым способом.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define DEF_INTERACTIVE "Interactive"
05. //+----------------+
06. #define def_Fibonacci_Element_Default   6
07. //+------------------------------------------------------------------+
08. void OnStart(void)
09. {
10.     Print("Result to ",
11. #ifdef DEF_INTERACTIVE
12.         DEF_INTERACTIVE, " : "
13. #else
14.         "Recursive : "
15. #endif
16.     , Fibonacci());
17. }
18. //+------------------------------------------------------------------+
19. uint Fibonacci(uint arg = def_Fibonacci_Element_Default)
20. {
21. #ifdef DEF_INTERACTIVE
22. 
23.     uint v, i, c;
24. 
25.     for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2)
26.         v += i;
27. 
28.     return (c == arg ? i : v);
29. 
30. #else    
31. 
32.     if (arg <= 1)
33.         return arg;
34. 
35.     return Fibonacci(arg - 1) +  Fibonacci(arg - 2);
36. 
37. #endif
38. }
39. //+------------------------------------------------------------------+

Код 09

В коде 09 мы используем директивы более организованно, поскольку можем управлять несколькими моментами, изменяя всего одну строку кода, как это было сделано в коде 08. Мы сделаем следующее: сначала запустим код, как показано выше, то есть с существующей строкой 4. Результат выполнения можно увидеть чуть ниже:

Рисунок 05

Если расположить строку 4 так же, как это было сделано в коде 08, то результат выполнения кода 09 будет таким:

Рисунок 06

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

То, что сделали в коде 09, было просто маленькой шуткой, чтобы показать, что из меньшего мы можем сделать гораздо больше. Я знаю, что многим кажется сложным понять то, как рассуждают программисты, но на самом деле всё не так сложно. Хорошие программисты всегда ищут способы сэкономить работу и повысить производительность. И в коде 09 мы делаем именно это, но в несколько более творческом ключе.

Думаю, вы поняли смысл директивы #ifdef, но давайте сделаем всё еще интереснее. Оператор IF, о котором мы рассказывали в другой статье, точно так же работает в директивах #ifdef и #ifndef. То есть всё, что находится внутри блока либо будет выполнено, либо нет. Однако в операторе if блок разграничен открывающей и закрывающей скобками. Здесь директива #ifdef или #ifndef закрывается директивой #endif. ВСЕГДА. Однако может случиться так, что мы что-то тестировали и не хотели повторять директивные операторы. В данном случае внутри построенного блока директивы #ifdef или #ifndef мы можем поместить директиву #else.

Обратите внимание: как и в случае с оператором if, когда значение выражения равно true и мы можем выполнить всё, что находится в else, связанном с данным оператором if, мы можем сделать то же самое и здесь, используя директивы #ifdef и #ifndef. То есть понимание того, как работает оператор IF, помогает нам лучше реализовывать и использовать похожие на него директивы, так как мы можем размещать вложенные директивы #ifdef и #ifndef для проверки определенных частей одного и того же кода, который мы хотим реализовать.

Хотя мы здесь этого не делаем, вы должны понимать, что это возможно. Давайте теперь вернемся к самому коду. Обратите внимание, что в строке 4 мы что-то определяем. При этом мы можем использовать директивы #ifdef и #ifndef для моделирования собственного кода. Но может возникнуть вопрос, не делаем ли мы во время этого определения в строке 4 то же самое, что и в строке 6, и не может ли это помешать директивам компиляции #ifdef и #ifndef. Нет, это не так. Это происходит так, потому что #ifdef и #ifndef проверяют, есть ли определение или нет. На самом деле существует директива #if, но не в MQL5, а на языке C и C++, где мы можем проверить значение, которое задаем в директиве. Однако я считаю, что из соображений простоты языка, директива #if не была включена, только директивы #ifdef и #ifndef.

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define DEF_INTERACTIVE
05. //+----------------+
06. #ifdef DEF_INTERACTIVE
07.     #define def_OPERATION "Interactive"
08. #else
09.     #define def_OPERATION "Recursive"
10. #endif
11. //+----------------+
12. #define def_Fibonacci_Element_Default   11
13. //+----------------+
14. #define def_MSG_TERMINAL "Result of " + def_OPERATION + " operation of element"
15. //+------------------------------------------------------------------+
16. void OnStart(void)
17. {
18.     Print(def_MSG_TERMINAL, " ", def_Fibonacci_Element_Default, " : ", Fibonacci());
19. }
20. //+------------------------------------------------------------------+
21. uint Fibonacci(uint arg = def_Fibonacci_Element_Default)
22. {
23. #ifdef DEF_INTERACTIVE
24. 
25.     uint v, i, c;
26. 
27.     for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2)
28.         v += i;
29. 
30.     return (c == arg ? i : v);
31. 
32. #else    
33. 
34.     if (arg <= 1)
35.         return arg;
36. 
37.     return Fibonacci(arg - 1) +  Fibonacci(arg - 2);
38. 
39. #endif
40. }
41. //+------------------------------------------------------------------+

Код 10

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

В любом случае, давайте посмотрим, что произойдет если выполнить код 10. Во-первых, давайте позволим директиве, указанной в строке 4, действительно выполнить свою работу. Для этого нам нужно скомпилировать код, как показано выше. В результате мы получим следующее:

Рисунок 07

Теперь попробуйте понять, что произошло до изменения кода, как показано в приведенном ниже фрагменте. Это значительно облегчит работу в будущем. Как только вы поймете, как был создан вывод, показанный на изображении 07, измените код, как показано в приведенном ниже фрагменте:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. // #define DEF_INTERACTIVE
05. //+----------------+
06. #ifdef DEF_INTERACTIVE
07.     #define def_OPERATION "Interactive"
08. #else
09.     #define def_OPERATION "Recursive"
10. #endif
11. //+----------------+
12. #define def_Fibonacci_Element_Default   11
13. //+----------------+
14. #define def_MSG_TERMINAL "Result of " + def_OPERATION + " operation of element"
15. //+------------------------------------------------------------------+
                   .
                   .
                   .

Фрагмент кода 10

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

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

Рисунок 08

Думаю, что теперь понятно, как использовать определение и директивы #ifdef, #ifndef, #else и #endif. Однако еще одно применение директивы #define еще предстоит обсудить. Помните, в начале статьи мы упоминали, что эта директива будет служить двум целям. Первая - та, которую мы рассмотрели в этой статье, и которую вы сможете отработать с помощью кодов в приложении. 

Это позволяет использовать директиву #define, избежать ненужного создания глобальной переменной, а также способствует простому, быстрому и эффективному анализу и реализации различных версий одного и того же кода. И это ценно для новичков. Для более опытных программистов это немного тривиально, поскольку они используют подобные вещи почти на автомате, так как это значительно облегчает жизнь. Жаль, что у нас нет директив #if из C и C++. Но это не страшно, всё так, как есть.

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

Прежде чем закончить эту статью, я хочу рассказать о последнем моменте. Это будет скорее БОНУС для вас, ведь у вас хватило терпения дочитать до конца, и наверняка, уже хотите начать экспериментировать с использованием директивы #define.

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

Возможно, вы этого не заметили, но когда мы используем директиву #define, мы сообщаем компилятору, что определенный текст должен быть заменен другим текстом. Благодаря данной концепции мы можем создать альтернативный синтаксис, при этом ничего не меняя в способе создания кодов.

Чтобы продемонстрировать это, давайте рассмотрим приведенный ниже код:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define BEGIN_PROC          {
05. #define END_PROC            }
06. #define RETURN              return
07. #define ENTER_POINT         void OnStart (void)
08. #define Z_SET_NUMBERS       long
09. #define EQUAL               ==
10. #define IS                  =
11. #define MINOR               <
12. #define OR                  ||
13. #define MORE                +=
14. //+------------------------------------------------------------------+
15. #define DEF_INTERACTIVE
16. //+----------------+
17. #ifdef DEF_INTERACTIVE
18.     #define def_OPERATION "Interactive"
19. #else
20.     #define def_OPERATION "Recursive"
21. #endif
22. //+----------------+
23. #define def_Fibonacci_Element_Default   11
24. //+----------------+
25. #define def_MSG_TERMINAL "Result of " + def_OPERATION + " operation of element"
26. //+------------------------------------------------------------------+
27. ENTER_POINT BEGIN_PROC  
28.     Print(def_MSG_TERMINAL, " ", def_Fibonacci_Element_Default, " : ", Fibonacci());
29. END_PROC
30. //+------------------------------------------------------------------+
31. Z_SET_NUMBERS Fibonacci(Z_SET_NUMBERS arg IS def_Fibonacci_Element_Default)
32. BEGIN_PROC  
33. #ifdef DEF_INTERACTIVE
34. 
35.     Z_SET_NUMBERS v, i, c;
36. 
37.     for (c IS 0, i IS 0, v IS 1; c MINOR arg; i MORE v, c MORE 2)
38.         v MORE i;
39. 
40.     RETURN (c EQUAL arg ? i : v);
41. 
42. #else    
43. 
44.     if ((arg EQUAL 1) OR (arg MINOR 1))
45.         RETURN arg;
46. 
47.     RETURN Fibonacci(arg - 1) + Fibonacci(arg - 2);
48. 
49. #endif
50. END_PROC
51. //+------------------------------------------------------------------+

Код 11

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

Но, глядя на него, можно подумать: "дружище, но ЭТО ЖЕ НЕ MQL5. Однако благодаря тому, что мы увидели в этой статье, можно взглянуть на него получше и увидеть, что да, хотя он и отличается, но код 11 - это чистый и простой MQL5, просто написанный по-другому, в основном ориентированный на то, чтобы напоминать менее математический и более естественный язык.

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


Заключительные идеи

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

В приложении вы найдете пять кодов, показанных здесь. Так что, хорошо вам провести время.

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

Прикрепленные файлы |
Anexo.zip (2.65 KB)
Нейросети в трейдинге: Фреймворк кросс-доменного прогнозирования временных рядов (TimeFound) Нейросети в трейдинге: Фреймворк кросс-доменного прогнозирования временных рядов (TimeFound)
В этой статье мы шаг за шагом собираем ядро интеллектуальной модели TimeFound, адаптированной под реальные задачи прогнозирования временных рядов. Если вас интересует практическая реализация нейросетевых патчинг-алгоритмов в MQL5 — вы точно по адресу.
Майнинг данных CFTC на Python и ИИ модель на их основе Майнинг данных CFTC на Python и ИИ модель на их основе
Попробуем смайнить даные CFTC, загрузить отчеты COT и TFF через Python, соединить это с котировками MetaTrader 5 и моделью ИИ и получить прогнозы. Что такое отчеты COT на рынке Форекс? Как использовать отчеты COT и TFF для прогнозирования?
Отбор признаков и снижение размерности с помощью анализа главных компонент (PCA) Отбор признаков и снижение размерности с помощью анализа главных компонент (PCA)
В статье рассматривается реализация модифицированного алгоритма анализа компонентов прямого отбора, вдохновленного исследованиями, представленными в книге Луки Пуггини (Luca Puggini) и Шона Маклуна (Sean McLoone) “Анализ компонентов прямого отбора: алгоритмы и приложения”.
Построение модели для ограничения диапазона сигналов по тренду (Часть 9): Советник с несколькими стратегиями (II) Построение модели для ограничения диапазона сигналов по тренду (Часть 9): Советник с несколькими стратегиями (II)
Количество стратегий, которые можно интегрировать в виде советника, практически безгранично. Однако каждая дополнительная стратегия увеличивает сложность алгоритма. Благодаря использованию нескольких стратегий советник может лучше адаптироваться к изменяющимся рыночным условиям, что потенциально повышает его прибыльность. Сегодня мы рассмотрим, как реализовать в MQL5 одну из выдающихся стратегий, разработанных Ричардом Дончианом, продолжая при этом совершенствовать функциональность нашего советника Trend Constraint.