
От начального до среднего уровня: Определения (II)
Введение
Представленные здесь материалы предназначены только для обучения. Ни в коем случае не рассматривайте его как окончательное приложение, целью которого не является изучение представленных концепций.
В предыдущей статье "От начального до среднего уровня: Определения (I)", мы рассказываем о директиве компиляции #define. Мы увидели, как использовать данную директиву для упрощения, ускорения и облегчения реализации нашего кода, а также как творчески и эффективно использовать ее на этапе изучения языка, чтобы сделать всё немного проще. Чтобы использовать этот ресурс, нужно понимать, что мы делаем. В общем, мы можем придать нашему MQL5-коду более экзотический вид. Хорошо.
По этой причине, существует второй способ использования директивы компиляции #define. Чтобы не усложнять изложенное в предыдущей статье, мы решили показать это в отдельной статье. Таким образом, мы сможем подойти к вопросу более спокойно. А вы сможете учиться и практиковаться в гораздо более приятной форме, что действительно облегчит понимание и усвоение концепций, поскольку данные знания будут иметь огромное значение для того, что мы увидим в следующих статьях, в основном когда мы начнем работать на уровне программирования, который я уже считаю промежуточным.
Что такое макрос
Если очень упростить, то макрос - это небольшая процедура, которую мы можем использовать несколько раз в коде. Это очень упрощенный взгляд на вещи. В действительности, чаще всего (и это распространяется на другие ситуации, которые немного сложнее) мы создаем макрос, когда часть кода повторяется почти что постоянно. Поэтому вместо того, чтобы постоянно писать один и тот же код, мы помещаем всё это в один блок или процедуру, которая называется макросом.
Однако данный способ определения макроса в действительности не является подходящим. Это связано с некоторыми факторами, которые еще больше усложняют создание такого определения.
Проблема в том, что в значительной степени (если не почти всегда) макросы определяются таким образом, что код размещается inline, а не внутри процедуры, что привело бы к вызовам, складывающим и раскладывающим элементы в памяти. На мой взгляд, это лучшее определение макроса. Но сказать, что с помощью макроса мы размещаем код inline, не значит сделать его каким-то особенным. А это связано с тем, что теоретически мы можем сделать это с любой процедурой или функцией. Я говорю "теоретически", потому что не заметил большой разницы во времени выполнения функций или процедур, объявленных как inline или нет.
Вы, вероятно, не представляете, о чем мы говорим. Позвольте немного прояснить ситуацию. Как и в C и C++, в MQL5 есть зарезервированное слово, которое я редко замечаю у других программистов, по крайней мере, здесь. Данное зарезервированное слово это inline. Но что это слово означает на практике? Что ж, обычно, когда программист создает код, он может преобразовать вызовы процедур или даже функций в код inline, если язык это позволяет. Иными словами, мы перестаем вызывать процедуры и вместо этого получаем код, который экспоненциально растет для того, чтобы ускорить его выполнение, и это означает использование большего объема памяти.
Это может показаться глупым или даже безумным. Однако при правильном использовании принуждение компилятора к созданию более быстрого кода взамен использования большего объема памяти может быть правильным решением. Однако при этом следует соблюдать осторожность, поскольку если код будет расти экспоненциально, то рано или поздно мы зайдем в тупик, так как нам потребуется всё больше и больше памяти, а производительность в скорости обработки не компенсирует затраты на увеличение объема памяти.
Для примера давайте посмотрим, как это можно сделать. Чтобы сделать это, посмотрим на код ниже.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Fibonacci_Element_Default 11 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. inline 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. //+------------------------------------------------------------------+
Код 01
Обратите внимание на объяснение, потому что очень важно правильно понять, что такое макрос, когда используем директиву компиляции #define для создания подпрограммы или процедуры, которая будет использоваться в нашем коде.
Обратите внимание, что код 01 очень похож на то, что мы уже видели в других статьях. Однако в строке 20 есть нечто другое. Данное отличие как раз и есть зарезервированное слово inline. Теперь когда знаем, как работает код 01, можно спросить: "Но что такого особенного в этом простом слове, добавленном в строке 20, которое делает код другим?
Ответ заключается в том, что код будет создан не так, как можно ожидать. Это связано с тем, что тот же самый код, если его перевести на C или C++, сгенерирует код немного другим. Дело не в том, что мы видим, а в том, как компилятор отнесется к такому способу написания кода.
Опять же, я предполагаю, что компилятор MQL5 делает всё таким образом, что нет заметной разницы между кодом, написанным так, как это сделано в 01, и другим кодом, увиденным в предыдущих статьях. Честно говоря, я не заметил никаких изменений в скорости обработки при использовании или отсутствии inline в объявлении процедуры или функции.
В любом случае, когда компилятор увидит строку 20, он поймет, что КАЖДЫЙ раз, когда данная функция Fibonacci_Interactive появляется в коде, ВЕСЬ КОД между строками 20 и 28 должен заменить вызов, который существует как Fibonacci_Interactive. При этом необходимо создать базу локальных переменных, присутствующих в процедуре или функции, чтобы они не конфликтовали с возможными локальными переменными, который находится в том месте, куда будет добавлен код.
Чтобы прояснить ситуацию, этот же 01-код будет собран компилятором в виде чего-то, похожего на показанное ниже:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Fibonacci_Element_Default 11 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. Print("Result: ", Fibonacci_Recursive()); 09. 10. { 11. uint v, i, c, arg = def_Fibonacci_Element_Default; 12. 13. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) 14. v += i; 15. 16. Print("Result: ", (c == arg ? i : v)); 17. 18. } 19. } 20. //+------------------------------------------------------------------+ 21. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default) 22. { 23. if (arg <= 1) 24. return arg; 25. 26. return Fibonacci_Recursive(arg - 1) + Fibonacci_Recursive(arg - 2); 27. } 28. //+------------------------------------------------------------------+
Код 02
Однако код 02 на самом деле является кодом, который будет создан и выполнен. Но, прошу заметить, что изменения в коде пришлось вносить весьма специфическим образом. И делать это будет компилятор. Мы, программисты, лишь указываем компилятору, должен он или не должен помещать функцию или процедуру inline. Но за тонкую настройку будет отвечать сам компилятор.
Ладно, сейчас вы, возможно, подумаете: "хорошо, причин для беспокойства нет, потому что, судя по всему, всё осталось по-прежнему". Действительно, мой дорогой читатель. Вот простой пример. Но учтите, что функция в строке 20 из кода 01, будет использоваться в нашем коде тысячу раз. Компилятор тысячу раз сделает то, что показано в коде 02. В результате исполняемый файл будет становиться всё больше и больше, занимать всё больше и больше места и требовать всё больше и больше времени для загрузки. Даже если бы скорость выполнения была выше, это не смогло бы ее компенсировать.
Итак, если вы это поняли, то понять, что такое макрос (как мы определим его в ближайшее время), - будет очень просто. Мы даже можем определить первый макрос с помощью данного кода. Обратите внимание, что между строками 10 и 18 у нас есть полный код, точнее, блок, изолированный от остального кода. Чтобы превратить данный код в наш первый макрос, мы просто изменим его на что-то вроде того, что видно ниже:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Fibonacci_Element_Default 11 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. Print("Result: ", Fibonacci_Recursive()); 09. 10. #define macro_Fibonacci_Interactive { \ 11. uint v, i, c, arg = def_Fibonacci_Element_Default; \ 12. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) v += i; \ 13. Print("Result: ", (c == arg ? i : v)); \ 14. } 15. } 16. //+------------------------------------------------------------------+ 17. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default) 18. { 19. if (arg <= 1) 20. return arg; 21. 22. return Fibonacci_Recursive(arg - 1) + Fibonacci_Recursive(arg - 2); 23. } 24. //+------------------------------------------------------------------+
Код 03
Теперь самое интересное: код 03 почти, скажем прямо, почти, такой же, как и код 02. Однако при его выполнении результат будет отличаться от кода 02. А вы поняли, в чем разница, дорогой читатель? Скорее всего, нет. Это связано с тем, что она не так очевидна при первом знакомстве с макросом.
Обратите теперь внимание на следующее: всё, что мы сделали отличающегося между кодами 02 и 03, - это добавили директиву компиляции #define в строке 10. За ним следует константа. Да, макрос ЯВЛЯЕТСЯ КОНСТАНТОЙ, никогда не забывайте об этом.
Макрос, когда он определен, должен содержаться в ОДНОЙ ЕДИНСТВЕННОЙ СТРОКЕ.
НЕВОЗМОЖНО создать макрос, который содержит более одной строки кода. Это правило продиктовано языком программирования. Таким образом, чтобы нам создать макрос из нескольких строк, в конце каждой строки необходимо добавить специальный символ. Этот символ можно увидеть на каждой из строк, начиная со строки 10, но будьте осторожны И НЕ РАЗМЕЩАЙТЕ ДАННУЮ СТРОКУ на последнюю, в противном случае макрос окажется не там, где мы ожидаем, и можно получить предупреждение об ошибке при попытке компиляции кода.
Кажется, всё достаточно просто, не так ли? Более или менее. Если быть внимательным, то да, создавать макросы довольно просто. Но если вы думаете, что это всё, что вы увидите в коде 03, то вы ошибаетесь. Это будет макрос, основанный на коде 02. Помните, что при выполнении кода 03 результат будет отличаться от кода 02. Но почему? Причина в том, что макрос НЕ ИСПОЛЬЗУЕТСЯ. Мы просто объявляем об этом. Таким образом, на выходе кода 02, как можно видеть на рисунке ниже, у нас будет два сообщения.
Рисунок 01
Однако при выполнении кода 03 выход будет таким:
Рисунок 02
Но как же тогда решить эту проблему? Для этого мы должны указать компилятору использовать макрос в коде. Это очень просто, как можно видеть ниже.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Fibonacci_Element_Default 11 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. Print("Result: ", Fibonacci_Recursive()); 09. 10. #define macro_Fibonacci_Interactive { \ 11. uint v, i, c, arg = def_Fibonacci_Element_Default; \ 12. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) v += i; \ 13. Print("Result: ", (c == arg ? i : v)); \ 14. } 15. 16. macro_Fibonacci_Interactive; 17. } 18. //+------------------------------------------------------------------+ 19. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default) 20. { 21. if (arg <= 1) 22. return arg; 23. 24. return Fibonacci_Recursive(arg - 1) + Fibonacci_Recursive(arg - 2); 25. } 26. //+------------------------------------------------------------------+
Код 04
Теперь это правильно. При включении строки 16 в код 04 получается тот же результат, что и на изображении 01, это говорит о том, что код работает так, как и ожидалось. Однако это была самая простая часть. Теперь подходим к самой интересной части: Давайте выделим отдельную тему для этого. Это необходимо, потому что мы должны сначала понять то, что было показано до этого момента, чтобы понять, что будет сделано дальше.
Передача аргументов макросу
"Господи! Я думал, что мы закончили, а тут вы объявляете, что можно передавать аргументы макросу". Давайте теперь проверим, правильно ли я понял концепцию макроса. Поправьте меня, если я ошибаюсь.
Макрос - это код, существующий в подпрограмме, которую мы хотим поместить в форме inline в наш код, не так ли? Так и есть. Вы правы. Таким образом, если мы сможем создать способ реализации целой функции или процедуры в макросе, нам не нужно будет создавать ту же функцию или процедуру, поскольку для компилятора при сборке кода это не будет иметь значения. Я прав? И опять же, правильно. Есть только одна маленькая деталь: реализовать процедуры проще, чем функции, поскольку функции в большинстве случаев требуют дополнительной переменной. В любом случае, это правильно.
"Тогда я понял. Если я хочу передать аргумент в макрос, я просто должен использовать тот же способ объявления, что и при объявлении функции или процедуры. Всё просто, для меня это очевидно".
Что ж, в данном случае вы почти правы. Это связано с тем, что мы не объявляем аргументы, передаваемые в макросе, так же, как это делается при использовании функции или процедуры. В этом случае всё работает немного по-другому, и именно здесь кроется сложность. В отличие от передачи параметров в функциях и процедурах, где мы можем сказать: "этим аргументом нельзя управлять", "этим можно", "этот аргумент такого-то типа" и "этот аргумент такого-то типа", в макросах мы НЕ ИМЕЕМ такого вида контроля. И, следовательно, чтобы избежать некоторых довольно неприятных ошибок, мы лишены помощи компилятора.
Разница заключается именно в этом. По этой причине многие программисты избегают использования макросов. Это связано с тем, что любая небрежность в коде может привести к серьезным неприятностям, так как ошибки в макросах очень сложно исправить именно потому, что их трудно найти. Дело в том, что в одной части кода макрос может выдавать правильные значения, а в другой части того же кода, чуть дальше, данный же макрос может выдавать неправильные значения. И уметь обнаружить и исправить этот тип сбоя,на мой взгляд, одна из самых сложных и изнурительных задач.
Поэтому будьте очень осторожны при использовании макросов в коде: они - ценный ресурс, но могут тратить часы на решение проблемы, которую в противном случае было бы легко устранить.
Тогда давайте теперь сделаем следующее: поскольку код 04 - это всего лишь модификация кода 01, в той степени, в которой мы можем использовать макросы, мы можем модифицировать код 04, чтобы понять, как передавать значения внутри макроса.
Прошу обратить внимание на следующее: В коде 01, в строках восемь и девять, у нас есть возможность использовать две разные функции: одну, где ответ будет рекурсивным, и другую, где ответ будет интерактивным. Но в коде 04, несмотря на то, что у нас есть оба ответа, мы НЕ МОЖЕМ передать значение в интерактивную вычислительную часть, если не изменим значение, указанное в определении в четвертой строке. Но это не соответствует действительности, ведь мы хотим иметь возможность передавать любое значение, как в случае с кодом 01.
Чтобы было легче понять сказанное, давайте посмотрим на код, приведенный ниже.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Fibonacci_Element_Default 11 05. //+------------------------------------------------------------------+ 06. #define macro_Fibonacci_Interactive { \ 07. uint v, i, c, arg = def_Fibonacci_Element_Default; \ 08. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) v += i; \ 09. Print("Result: ", (c == arg ? i : v)); \ 10. } 11. //+----------------+ 12. void OnStart(void) 13. { 14. Print("Result: ", Fibonacci_Recursive(15)); 15. macro_Fibonacci_Interactive; 16. } 17. //+------------------------------------------------------------------+ 18. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default) 19. { 20. if (arg <= 1) 21. return arg; 22. 23. return Fibonacci_Recursive(arg - 1) + Fibonacci_Recursive(arg - 2); 24. } 25. //+------------------------------------------------------------------+
Код 05
Запустив код 05, мы увидим в результате то, что показано на изображении ниже.
Рисунок 03
Очевидно, что результаты разные. Это происходит так, потому что в строке 14 кода 05 мы передаем значение в качестве параметра функции в строке 18. В макросе это не так, поскольку значение фиксировано в определении строки 4. Итак, как видите, работа с макросами требует некоторой осторожности и повышенного внимания. Чтобы решить данную проблему, нужно передать макросу некоторый аргумент. Для этого мы немного изменим код макроса. Это изменение можно увидеть в приведенном ниже коде:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Fibonacci_Element_Default 11 05. //+------------------------------------------------------------------+ 06. #define macro_Fibonacci_Interactive(arg) { \ 07. uint v, i, c; \ 08. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) v += i; \ 09. Print("Result: ", (c == arg ? i : v)); \ 10. } 11. //+----------------+ 12. void OnStart(void) 13. { 14. Print("Result: ", Fibonacci_Recursive(15)); 15. macro_Fibonacci_Interactive(14); 16. } 17. //+------------------------------------------------------------------+ 18. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default) 19. { 20. if (arg <= 1) 21. return arg; 22. 23. return Fibonacci_Recursive(arg - 1) + Fibonacci_Recursive(arg - 2); 24. } 25. //+------------------------------------------------------------------+
Код 06
Когда код 06 будет выполнен, результат будет таким:
Рисунок 04
Понятно, что значения разные, но мы это сделали специально, чтобы показать, что мы можем передавать независимые значения и что одно не связано с другим. Таким образом, становится ясно, что макрос не связан ни с какой другой функцией или процедурой.
Это было интересно, но не увлекательно, поскольку макрос не ведет себя ни как функция, как это было бы в коде 01, ни как отдельная процедура. В действительности это просто изолированный код. Однако код 06 по-прежнему работает так, как если бы это был код 02, а это, в большинстве случаев, совершенно бесполезно, поскольку не служит абсолютно никакой цели, кроме размещения того, что было бы строкой 15 из кода 06, в различных других точках. Но, поскольку эти коды имеют дидактическую цель, они просты и не нуждаются в использовании такого инструмента, каким являются макросы. Тем не менее, мы делаем это здесь именно для того, чтобы объяснить, как его использовать и работать с ним.
Давайте подумаем, как сделать так, чтобы макрос вел себя как функция. На самом деле, мы НЕ МОЖЕМ использовать в качестве функции макрос, подобный тому, что приведен в коде 06. Если помните, функция - это как специальная переменная: она всегда возвращает значение, когда мы используем ее для получения значения на основе переданных ей аргументов. Обратите внимание на то, что мы сказали. Я НЕ УТВЕРЖДАЮ, что мы не можем использовать макросы в качестве функций. Я хочу сказать, что МЫ НЕ МОЖЕМ использовать ДАННЫЙ МАКРОС так, как если бы он был функцией. Будьте осторожны, не перепутайте овец с козами.
Однако, несмотря на это первоначальное ограничение, мы можем заставить ЭТОТ МАКРОС вести себя как процедуру, в которой мы выполняем шаг по ссылке. Как? Всё просто: изменим код так, как показано ниже:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Fibonacci_Element_Default 11 05. //+------------------------------------------------------------------+ 06. #define macro_Fibonacci_Interactive(arg) { \ 07. uint v, i, c; \ 08. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) v += i; \ 09. arg = (c == arg ? i : v); \ 10. } 11. //+----------------+ 12. void OnStart(void) 13. { 14. ulong value; 15. 16. Print("Result: ", Fibonacci_Recursive((uint)(value = 15))); 17. macro_Fibonacci_Interactive(value); 18. Print("Checking: ", value); 19. } 20. //+------------------------------------------------------------------+ 21. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default) 22. { 23. if (arg <= 1) 24. return arg; 25. 26. return Fibonacci_Recursive(arg - 1) + Fibonacci_Recursive(arg - 2); 27. } 28. //+------------------------------------------------------------------+
Код 07
Теперь всё становится интересным. Дело в том, что мы начали использовать макрос, так сказать, для более благородных целей. Обратите внимание на следующее: в строке 14 мы объявляем переменную, но МЫ НЕ ИНИЦИАЛИЗИРУЕМ ЕЕ. Мы инициализируем ее в строке 16. Вы можете спросить: "А что это за безумие происходит на строке 16? Не волнуйтесь, это не безумие.
Прошу заметить, что переменная в строке 14 имеет тип ulong, а тип, ожидаемый функцией в строке 21, имеет тип uint. Поскольку мы хотим использовать одно и то же значение и для функции в строке 21, и для макроса, который будет использоваться ниже, нам нужно провести явное преобразование типов, чтобы компилятор не выдавал предупреждений. По этой причине объявление строки 16 выглядит так.
Хорошо, сразу после этого у нас есть строка 17. Вот тут-то и начинается самое интересное. Такое происходит, потому что мы передаем переменную по ссылке. Таким образом, когда макрос изменяет ее значение, данное изменение будет отражено здесь, в нашем основном коде. Изменение значения происходит в строке 9, поэтому необходимо быть бдительным. В противном случае мы можем получить бомбу замедленного действия. И, чтобы продемонстрировать, что эта система работает и ее можно использовать, у нас есть строка 18, где значение переменной выводится на терминал. Выполнив код 07, мы получим изображение, показанное ниже:
Рисунок 05
Хорошо, думаю, понятно, что всякий раз, когда мы передаем переменную в макрос, мы делаем это с помощью шага по ссылке. И именно поэтому мы должны быть осторожны, чтобы макрос не изменил значение неправильно. Но подождите секундочку. Тип макроса, который мы видим в коде 07, - макрос, который работает как процедура. Есть ли способ использовать макрос как функцию? То есть мы можем отправить одно значение и получить в ответ другое. Данный вопрос возникает у многих программистов в самом начале изучения макросов.
В принципе, макросы больше ориентированы на процедуры, но в зависимости от того, как мы построим код внутри макроса, мы можем заставить его работать так, как если бы он был функцией. В данном случае мы рассматриваем коды, представленные в этих статьях, мы можем создать пример этого, просто как способ представления механизма. Несмотря на то, что он не очень продуман и может показаться не очень интересным, можно попробовать. Пример этого можно увидеть в приведенном ниже коде:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Fibonacci_Element_Default 11 05. //+------------------------------------------------------------------+ 06. #define macro_Ternário(A, B, C, D) (A == B ? C : D) 07. //+----------------+ 08. #define macro_Fibonacci_Interactive(arg) { \ 09. uint v, i, c; \ 10. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) v += i; \ 11. arg = macro_Ternário(c, arg, i, v); \ 12. } 13. //+----------------+ 14. void OnStart(void) 15. { 16. ulong value; 17. 18. Print("Result: ", Fibonacci_Recursive((uint)(value = 15))); 19. macro_Fibonacci_Interactive(value); 20. Print("Checking: ", value); 21. } 22. //+------------------------------------------------------------------+ 23. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default) 24. { 25. if (arg <= 1) 26. return arg; 27. 28. return Fibonacci_Recursive(arg - 1) + Fibonacci_Recursive(arg - 2); 29. } 30. //+------------------------------------------------------------------+
Код 08
Здесь, в коде 08, мы можем увидеть небольшую демонстрацию макроса, который работает как функция. Данный макрос, определенный в шестой строке, является простым примером того, как макрос может работать как функция. Прошу заметить, что в строке 11 мы используем его. В принципе, это позволяет скрыть некоторые сложности, которые могут существовать в коде, поскольку в зависимости от имени, которое мы даем макросу, гораздо проще понять, как он работает.
Обратите внимание, что цель здесь НЕ В ТОМ, ЧТОБЫ сделать код более эффективным, а в том, чтобы сделать его более читабельным. Например, можно создать набор макросов для манипулирования значениями даты и времени, чтобы использовать тип datetime. Это типичный пример для макросов с функциональностью функции. Чтобы сделать это более наглядным, давайте создадим несколько макросов, чтобы продемонстрировать то, о чем мы говорим.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. enum eConvert { 05. FORMAT_DECIMAL, 06. FORMAT_OCTAL, 07. FORMAT_HEX, 08. FORMAT_BINARY 09. }; 10. //+------------------------------------------------------------------+ 11. #define macro_GetDate(A) (A - (A % 86400)) 12. #define macro_GetTime(A) (A % 86400) 13. //+----------------+ 14. #define macro_GetSec(A) (A - (A - (A % 60))) 15. #define macro_GetMin(A) (int)((A - (A - ((A % 3600) - (A % 60)))) / 60) 16. #define macro_GetHour(A) (int)((A - (A - ((A % 86400) - (A % 3600)))) / 3600) 17. //+----------------+ 18. #define PrintX(X) Print(#X, " => ", X); 19. //+------------------------------------------------------------------+ . . .
Фрагмент 01
В фрагменте 01 мы имеем то, что будет нашим включаемым файлом. Я включаю макросы, чтобы показать, что со временем мы можем существенно расширить наши возможности программирования, по мере накопления опыта и создания определенной библиотеки.
Чтобы проверить, что делают данные макросы между строками 11 и 18, давайте воспользуемся небольшим тестовым кодом. Его можно увидеть чуть ниже:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "Tutorial\File 01.mqh" 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. datetime dt = D'2024.08.15 12:30:27'; 09. ulong v; 10. 11. Print("Date And Time: ", dt); 12. Print("0x", ValueToString(dt, FORMAT_HEX)); 13. PrintX((datetime)(v = macro_GetDate(dt))); 14. Print("0x", ValueToString(v, FORMAT_HEX), " :: ", ValueToString(v, FORMAT_DECIMAL)); 15. PrintX((datetime)(v = macro_GetTime(dt))); 16. Print("0x", ValueToString(v, FORMAT_HEX), " :: ", ValueToString(v, FORMAT_DECIMAL)); 17. PrintX((datetime)(v = macro_GetSec(dt))); 18. Print("0x", ValueToString(v, FORMAT_HEX), " :: ", ValueToString(v, FORMAT_DECIMAL)); 19. PrintX((datetime)(v = macro_GetMin(dt))); 20. Print("0x", ValueToString(v, FORMAT_HEX), " :: ", ValueToString(v, FORMAT_DECIMAL)); 21. PrintX((datetime)(v = macro_GetHour(dt))); 22. Print("0x", ValueToString(v, FORMAT_HEX), " :: ", ValueToString(v, FORMAT_DECIMAL)); 23. } 24. //+------------------------------------------------------------------+
Код 09
Это очень интересно и увлекательно, поскольку, без особых усилий, мы можем проанализировать ряд вещей, которые в противном случае пришлось бы делать с помощью других ресурсов, которые еще не были объяснены. Когда запускаем код 09, он отобразит следующее:
Рисунок 06
Теперь вы поймете, почему я сказал, что код 09 весьма интересен. В строках 13, 15, 17, 19 и 21 у нас есть вызовы макроса. Этот макрос определен в строке 18 фрагмента 01. Цель макроса - сообщить нам имя переменной и ее значение. В данном случае имя переменной - это функция, которая использует разные макросы в каждой из строк, упомянутых в коде 09. Обратите внимание, что это довольно необычная и интересная комбинация, поскольку значение, возвращаемое каждым макросом, помещается в локальную переменную, а сразу после этого выводится шестнадцатеричное значение данной переменной, а также ее десятичное значение.
Это показывает, что мы действительно захватываем правильные значения в формате времени даты. Я знаю, что многие из вас могут подумать, что это нонсенс, но, как видите, этот макрос очень полезен, особенно когда мы хотим, чтобы код выполнялся как можно быстрее и безопаснее, и делает всю работу по написанию кода значительно проще, приятнее и увлекательнее.
Заключительные идеи
В этой статье мы рассмотрели, что это такое и как следует относиться к макросам для написания кода, поскольку они являются одним из ресурсов, которые часто используются неправильно, а в других случаях и вовсе игнорируются, именно из-за недостатка знаний и практики их использования. Многие программисты в итоге не понимают, почему то, что они считали невозможным (или даже недостижимым) может сделать другой, часто неизвестный программист.
Я сам придерживаюсь (и мне нравится так думать) следующего тезиса:
Плохих программистов не бывает, есть только программисты, которые не нашли свой путь. Да, есть и неподготовленные профессионалы, и профессионалы, которые считают себя подготовленными, а на самом деле они еще новички в программировании.
Подобная концепция, которую я пытаюсь донести до вас в этих статьях, предназначена именно для тех, кто только начинает изучать программирование. Если вы начнете с правильного пути, с правильным пониманием концепций и причин существования того или иного инструмента, вам будет легче воплотить свои идеи в реальность. Так что, уважаемые читатели, вы можете счесть данный материал довольно глупым и не имеющим особой цели или задачи, но если вы поймете и примените на практике то, о чем здесь говорилось, вы обнаружите, что многое из того, что раньше казалось вам сложным и трудным, станет вполне достижимым. Конечно, кое-что еще предстоит пояснить, но мы уже сделали много шагов в правильном направлении.
Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/15588





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования