От начального до среднего уровня: Указатель на функцию
Введение
В предыдущей статье От начального до среднего уровня: Объекты (II), мы начали работать с первым типом событий, который можно использовать для манипулирования объектом, представленным на графике.
Однако там использовался тип события, который по умолчанию всегда срабатывает в MetaTrader 5 при взаимодействии пользователя с графиком. В данном случае речь идёт о нажатии клавиши. Поскольку это событие легко перехватить с помощью обработчика OnChartEvent, вы, возможно, думаете, что не существует другого способа использовать клавиатуру, кроме как через перехват данного события. Что ж, уважаемый читатель, на самом деле всё обстоит не совсем так. Хотя скрипты не используют и не допускают использование обработчика OnChartEvent, мы можем включить механизмы для управления определенными свойствами объекта с помощью клавиатуры и скрипта, даже если это несколько необычный подход.
Итак, прежде чем рассматривать обработку событий мыши, давайте посмотрим, как обрабатывать события клавиатуры при использовании скриптов. Хотя MetaTrader 5 и, следовательно, MQL5 не предназначены для такого рода операций, ведь они разработаны для работы с ценовыми графиками, важно, чтобы вы знали, что можно, а что нельзя делать. Существуют ограничения, которые необходимо понимать.
Скрипт с событиями?
Для начала вам нужно понять: МЫ НЕ МОЖЕМ ИМЕТЬ СКРИПТ С СОБЫТИЯМИ. Но это не мешает нам создать скрипт, способный обрабатывать события, которые поступают с клавиатуры. Однако, и вот здесь-то всё действительно усложняется, необходимо, чтобы вы понимали, что MQL5 НЕ ПРЕДНАЗНАЧЕН для определенных типов реализаций. Поэтому при программировании чего-либо исключительно с использованием MQL5 возникают различные ограничения и трудности.
Но как можно перехватывать и обрабатывать события клавиатуры внутри скрипта? Правда в том, что МЫ НЕ МОЖЕМ. Что мы действительно можем сделать, так это перехватывать определенные клавиши и, используя своего рода фильтрацию, создавать нечто похожее на обработчик событий клавиатуры. Хотя на самом деле мы будем использовать обработчик OnChartEvent.
Чтобы вам было понятнее, мы воспользуемся кодом, который рассматривали в предыдущей статье. Таким образом, будет гораздо проще понять, с какой именно проблемой мы можем столкнуться. Код, о котором идёт речь, приведён ниже.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_KEY_UP 38 05. #define def_KEY_DOWN 40 06. //+----------------+ 07. #define macro_NameObject "Demo" + (string)ObjectsTotal(0) 08. //+------------------------------------------------------------------+ 09. string gl_Objs[2]; 10. //+------------------------------------------------------------------+ 11. int OnInit() 12. { 13. ObjectCreate(0, gl_Objs[0] = macro_NameObject, OBJ_VLINE, 0, 0, 0); 14. ObjectSetInteger(0, gl_Objs[0], OBJPROP_COLOR, clrRoyalBlue); 15. ObjectCreate(0, gl_Objs[1] = macro_NameObject, OBJ_HLINE, 0, 0, 0); 16. ObjectSetInteger(0, gl_Objs[1], OBJPROP_COLOR, clrPurple); 17. 18. return INIT_SUCCEEDED; 19. }; 20. //+------------------------------------------------------------------+ 21. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 22. { 23. return rates_total; 24. }; 25. //+------------------------------------------------------------------+ 26. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 27. { 28. static int p = 0; 29. MqlRates rate[1]; 30. 31. switch(id) 32. { 33. case CHARTEVENT_KEYDOWN: 34. switch ((int)lparam) 35. { 36. case def_KEY_DOWN: 37. p = (p < Bars(_Symbol, _Period) ? p + 1 : p); 38. break; 39. case def_KEY_UP: 40. p = (p > 0 ? p - 1 : p); 41. break; 42. default: 43. return; 44. } 45. Comment(StringFormat("Current bar analyzed: %d", p)); 46. CopyRates(_Symbol, _Period, p, rate.Size(), rate); 47. ObjectMove(0, gl_Objs[0], 0, rate[0].time, rate[0].close); 48. ObjectMove(0, gl_Objs[1], 0, rate[0].time, rate[0].close); 49. break; 50. } 51. ChartRedraw(); 52. }; 53. //+------------------------------------------------------------------+ 54. void OnDeinit(const int reason) 55. { 56. Comment(""); 57. for (uint c = 0; c < gl_Objs.Size(); c++) 58. ObjectDelete(0, gl_Objs[c]); 59. ChartRedraw(); 60. }; 61. //+------------------------------------------------------------------+
Код 01
При выполнении кода 01 будет получен такой результат:

Анимация 01
Теперь вопрос: как можно создать нечто похожее на код 01, с идентичным результатом, как в анимации 01, но используя для этого скрипт? Хорошо, именно на этот вопрос мы собираемся ответить.
Для достижения такой цели нам потребуется использовать различные вызовы из библиотеки MQL5. Однако, поскольку мы хотим сосредоточиться исключительно на MQL5, мы не будем использовать здесь никакую другую методологию. Хотя использование методологии с применением кода на C или C++ могло бы значительно упростить задачу, мы не будем этого делать здесь, в статьях, ориентированных на уровень от базового до среднего.
Возможно, в будущем, если я решу показать, как разрабатывать более продвинутый код, где необходимо использовать программы, написанные и скомпилированные на других языках, мы разберем ту же самую проблему, но уже более простым способом. Однако это также гораздо сложнее, поскольку для обеспечения взаимодействия между языками и, следовательно, для решения поставленной задачи, необходимо хорошо разбираться в MQL5, а также в другом языке программирования.
Итак, вернемся к нашей главной проблеме. Для начала: здесь мы не будем использовать структурный код. Это связано с тем, что наша цель — не создать приложение, а показать, как решить проблему. Поэтому нам необходимо разработать план действий. И для этого первым делом нужно определить, каким будет начальный скрипт.
Как видите, и в коде, и в анимации у нас есть два объекта: один — горизонтальная линия, а другой — вертикальная. Оба будут связаны со временем создания бара, а также с ценой закрытия. И когда скрипт завершит свою работу — причём сделает это корректно — нам нужно будет удалить оба объекта, созданных этим скриптом.
Отлично, теперь у нас есть первоначальный план действий. После этого мы можем приступить к реализации исходного кода. Это показано ниже:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_KEY_UP 38 05. #define def_KEY_DOWN 40 06. //+----------------+ 07. #define macro_NameObject "Demo" + (string)ObjectsTotal(0) 08. //+------------------------------------------------------------------+ 09. string gl_Objs[2]; 10. //+------------------------------------------------------------------+ 11. void OnStart(void) 12. { 13. int p = 0; 14. MqlRates rate[1]; 15. 16. ObjectCreate(0, gl_Objs[0] = macro_NameObject, OBJ_VLINE, 0, 0, 0); 17. ObjectSetInteger(0, gl_Objs[0], OBJPROP_COLOR, clrRoyalBlue); 18. ObjectCreate(0, gl_Objs[1] = macro_NameObject, OBJ_HLINE, 0, 0, 0); 19. ObjectSetInteger(0, gl_Objs[1], OBJPROP_COLOR, clrPurple); 20. 21. Comment(StringFormat("Current bar analyzed: %d", p)); 22. CopyRates(_Symbol, _Period, p, rate.Size(), rate); 23. ObjectMove(0, gl_Objs[0], 0, rate[0].time, rate[0].close); 24. ObjectMove(0, gl_Objs[1], 0, rate[0].time, rate[0].close); 25. 26. Comment(""); 27. for (uint c = 0; c < gl_Objs.Size(); c++) 28. ObjectDelete(0, gl_Objs[c]); 29. ChartRedraw(); 30. } 31. //+------------------------------------------------------------------+
Код 02
Этот код 02 дает начало нашему "коду атаки". Однако, если попытаемся запустить его, мы не увидим никаких результатов. Причина в том, что объекты создаются, размещаются и удаляются до того, как мы успеваем осознать их фактическое присутствие.
Уважаемый читатель, здесь, в коде 02, я хочу обратить ваше внимание на один момент. Внимательно посмотрите на каждую строку кода 02 и вы увидите, что те же самые строки присутствуют и в коде 01. Но мы ничего здесь не видим. Причина в том, что фрагмент кода между строками 21 и 24, который является частью обработчика события OnChartEvent, показанного в коде 01, выполняется здесь слишком быстро, не предоставляя нам никакой возможности для взаимодействия. Именно в данном фрагменте нам и нужно вмешаться, чтобы реализовать обработку событий клавиатуры.
Идеально. Мы уже знаем, как будет выглядеть исходный код, пора разбить задачу на более мелкие части. Потому что создание монолитных кодов — это очень утомительное занятие. Таким образом, код 02 изменится, чтобы стать кодом 03.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_KEY_UP 38 05. #define def_KEY_DOWN 40 06. //+----------------+ 07. #define macro_NameObject "Demo" + (string)ObjectsTotal(0) 08. //+------------------------------------------------------------------+ 09. string gl_Objs[2]; 10. //+------------------------------------------------------------------+ 11. void OnStart(void) 12. { 13. Init(); 14. KeyEvent(def_KEY_DOWN); 15. Deinit(); 16. } 17. //+------------------------------------------------------------------+ 18. void Init(void) 19. { 20. ObjectCreate(0, gl_Objs[0] = macro_NameObject, OBJ_VLINE, 0, 0, 0); 21. ObjectSetInteger(0, gl_Objs[0], OBJPROP_COLOR, clrRoyalBlue); 22. ObjectCreate(0, gl_Objs[1] = macro_NameObject, OBJ_HLINE, 0, 0, 0); 23. ObjectSetInteger(0, gl_Objs[1], OBJPROP_COLOR, clrPurple); 24. } 25. //+------------------------------------------------------------------+ 26. void KeyEvent(int lparam) 27. { 28. static int p = 0; 29. MqlRates rate[1]; 30. 31. switch (lparam) 32. { 33. case def_KEY_DOWN: 34. p = (p < Bars(_Symbol, _Period) ? p + 1 : p); 35. break; 36. case def_KEY_UP: 37. p = (p > 0 ? p - 1 : p); 38. break; 39. default: 40. return; 41. } 42. Comment(StringFormat("Current bar analyzed: %d", p)); 43. CopyRates(_Symbol, _Period, p, rate.Size(), rate); 44. ObjectMove(0, gl_Objs[0], 0, rate[0].time, rate[0].close); 45. ObjectMove(0, gl_Objs[1], 0, rate[0].time, rate[0].close); 46. } 47. //+------------------------------------------------------------------+ 48. void Deinit(void) 49. { 50. Comment(""); 51. for (uint c = 0; c < gl_Objs.Size(); c++) 52. ObjectDelete(0, gl_Objs[c]); 53. ChartRedraw(); 54. } 55. //+------------------------------------------------------------------+
Код 03
Теперь у нас гораздо более приятный код, настоящее наслаждение. А всё потому, что при взгляде на код 03, можно увидеть то, что нам действительно нужно сделать. Нам нужно, чтобы в строке 14 выполнялся какой-то цикл, чтобы мы могли взаимодействовать с объектами и управлять ими, как показано в анимации 01. Вопрос в том, как мы можем это сделать? Итак, вот она, самая интересная часть. Но прежде, чем мы приступим к этому, я хочу напомнить вам, что циклы опасны. Это уже объяснялось, также как и способ безопасного выхода из циклов.
И поскольку каждый скрипт безвозвратно удаляется с графика, как только MetaTrader 5 потребуется перестроить график с нуля, например, когда пользователь попросит MetaTrader 5 изменить период графика, скрипт будет удален в этот момент. Однако, и это важно понимать, простого удаления скрипта недостаточно. Также необходимо убрать созданные им объекты. В противном случае эти объекты снова появятся на графике, как только MetaTrader 5 начнет его перерисовывать.
Я знаю, что это может звучать довольно шокирующе, но я говорю вам это, чтобы вы были внимательны при использовании объектов внутри скрипта. Если вы не хотите, чтобы объекты, созданные скриптом, оставались на графике после завершения его работы, вам следует их удалить. В ином случае, просто проигнорируйте этот этап исключения, и вопрос будет решен.
Итак, теперь давайте перейдем к теме циклов. Проблема здесь не в самом создании цикла, а в том, чтобы не сделать его слишком агрессивным и не потреблять слишком много ресурсов процессора. Поскольку события клавиатуры — каким бы безумным ни был пользователь — происходят не каждое мгновение, мы можем добавить некоторые элементы в тело цикла. Таким образом, у нас получится скрипт, который будет гораздо менее ресурсоемким с точки зрения процессора. Таким образом, после анализа этих возможностей мы получаем это:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_KEY_UP 38 05. #define def_KEY_DOWN 40 06. //+----------------+ 07. #define macro_NameObject "Demo" + (string)ObjectsTotal(0) 08. //+------------------------------------------------------------------+ 09. string gl_Objs[2]; 10. //+------------------------------------------------------------------+ 11. void OnStart(void) 12. { 13. Init(); 14. while (!IsStopped()) 15. { 16. if (TerminalInfoInteger(TERMINAL_KEYSTATE_UP)) 17. KeyEvent(def_KEY_UP); 18. if (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN)) 19. KeyEvent(def_KEY_DOWN); 20. Sleep(100); 21. } 22. Deinit(); 23. } 24. //+------------------------------------------------------------------+ 25. void Init(void) 26. { 27. ObjectCreate(0, gl_Objs[0] = macro_NameObject, OBJ_VLINE, 0, 0, 0); 28. ObjectSetInteger(0, gl_Objs[0], OBJPROP_COLOR, clrRoyalBlue); 29. ObjectCreate(0, gl_Objs[1] = macro_NameObject, OBJ_HLINE, 0, 0, 0); 30. ObjectSetInteger(0, gl_Objs[1], OBJPROP_COLOR, clrPurple); 31. } 32. //+------------------------------------------------------------------+ 33. void KeyEvent(int lparam) 34. { 35. static int p = 0; 36. MqlRates rate[1]; 37. 38. switch (lparam) 39. { 40. case def_KEY_DOWN: 41. p = (p < Bars(_Symbol, _Period) ? p + 1 : p); 42. break; 43. case def_KEY_UP: 44. p = (p > 0 ? p - 1 : p); 45. break; 46. default: 47. return; 48. } 49. Comment(StringFormat("Current bar analyzed: %d", p)); 50. CopyRates(_Symbol, _Period, p, rate.Size(), rate); 51. ObjectMove(0, gl_Objs[0], 0, rate[0].time, rate[0].close); 52. ObjectMove(0, gl_Objs[1], 0, rate[0].time, rate[0].close); 53. } 54. //+------------------------------------------------------------------+ 55. void Deinit(void) 56. { 57. Comment(""); 58. for (uint c = 0; c < gl_Objs.Size(); c++) 59. ObjectDelete(0, gl_Objs[c]); 60. ChartRedraw(); 61. } 62. //+------------------------------------------------------------------+
Код 04
Теперь посмотрите на цикл в строке 14. Таким образом, мы получаем поведение, которое можно увидеть в анимации ниже.

Анимация 02
Интересно, не правда ли? Но это еще не всё. Есть еще материал для демонстрации. Его можно увидеть в анимации ниже.

Анимация 03
Прошу заметить, уважаемый читатель: когда мы запрашиваем изменение периода графика, MetaTrader 5 удаляет график и немедленно создает его заново. Поскольку скрипты в MetaTrader 5 не перезагружаются по умолчанию, работа скрипта завершится, а созданные им объекты будут удалены с графика — это именно то, чего мы хотели добиться в коде 04, представленном выше.
Но вы, возможно, задаетесь вопросом: зачем использовать функции в строках 16 и 18? Причина в том, чтобы не использовать абсолютно ничего, что не является частью стандартной библиотеки MQL5. Поскольку за ненадобностью MQL5 не предоставляет никаких функций или процедур для прямого чтения нажатых клавиш, мы не можем считывать их более универсальным способом. Это необходимо для создания механизма, эквивалентного тому, который мы бы использовали при перехвате события ChartEvent и захвате CHARTEVENT_KEYDOWN, как показано в коде 01.
Однако это не означает, что мы не можем использовать данный тип чтения с клавиатуры. Для этого потребовалось бы использовать ресурсы, которые не входят в состав MQL5, и поэтому мы не будем рассматривать их в данных статьях, ориентированных на уровень от базового до среднего.
Хорошо, но вы можете заметить, что в коде 04 мы используем ресурсы или перечисление, которые значительно упрощают понимание того, какой тип клавиши ожидается. Но в то же время мы видим, что нам не обязательно настраивать всё так, как показано в коде 04. Мы можем что-то немного улучшить. И в данном случае, поскольку мы не хотим показывать слишком простую и неинтересную модификацию, мы воспользуемся возможностью объяснить ещё один ресурс, доступный в MQL5. Это нечто с очень специфической целью, и у нас не так много возможностей показать, как работает этот ресурс, и данный случай один из них. Итак, чтобы правильно разделить темы, перейдём к новой.
Указатель на функцию
Это одна из тех редких возможностей, которые у нас есть, чтобы объяснить ресурс с очень специфической целью в MQL5. Поскольку в предыдущей теме мы реализовали, скажем так, альтернативную версию того, что ранее было представлено как индикатор, теперь у нас есть возможность объяснить указатели на функции. Это очень интересный ресурс, но он вызывает огромную путаницу в голове любого начинающего программиста, особенно если у него нет четкого представления о некоторых понятиях.
Для начала: мы уже довольно давно не упоминали о необходимости каких-либо предварительных условий для понимания темы. Это связано с тем, что темы всегда были привязаны к какой-либо из недавних статей. Однако это один из тех случаев, когда используемые нами понятия встречаются в относительно давних статьях. В данном случае речь идёт о статье От начального до среднего уровня: Переменные (III) . Данная статья завершает рассмотрение темы, связанной с переменными и константами. Чёткое понимание изложенной там концепции будет иметь первостепенное значение для того, чтобы разобраться в наших дальнейших действиях здесь.
Для начала давайте рассмотрим очень простой и, казалось бы, нелепый пример. Однако он поможет нам понять, как работать с этим ресурсом, который мы собираемся изучить. Исходный код можно увидеть ниже.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. string Msg_01(const int value) 05. { 06. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 07. } 08. //+------------------------------------------------------------------+ 09. string Msg_02(const int value) 10. { 11. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 12. } 13. //+------------------------------------------------------------------+ 14. void OnStart(void) 15. { 16. Print(Msg_01(171)); 17. Print(Msg_02(-375)); 18. } 19. //+------------------------------------------------------------------+
Код 05
Хорошо, код 05 очень простой, поэтому мы не будем его объяснять. Но результат его реализации можно увидеть ниже.

Изображение 01
Хотя код 05 очень прост, он содержит элементы, которые нам интересно узнать перед тем, как переходить к чему-то более сложному. Главный вопрос здесь: какой механизм мы могли бы создать для связи функции, определенной в строке 04, с функцией, определенной в строке 09? Это позволяет выбрать, какую функцию использовать, но без прямого указания её названия.
Хорошо, возможно, вы думаете о перегрузке, о которой говорилось в статье От начального до среднего уровня: Перегрузка . Тем не менее, перегрузка здесь не применима, по крайней мере, не в том виде, в котором мы хотим её использовать. В действительности, наиболее близким к идеалу механизмом были бы массивы. В статьях От начального до среднего уровня: Массив (IV) мы простым языком объяснили, как работать с массивами. Там основное внимание уделялось хранению дискретных величин, таких как целые числа и числа с плавающей запятой. Однако в массивах можно хранить и другие типы информации. В данном случае, функции или процедуры. "Подождите, но как это возможно? Вы что, с ума сошли? Как можно хранить функции и процедуры в массиве? Это совершенно бессмысленно".
Именно поэтому мы и упомянули, что вам необходимо очень хорошо разбираться в таком понятии, как переменная. Существует особый тип переменных, называемый указателем. Поскольку из соображений безопасности и простоты MQL5 не реализует или, лучше сказать, не позволяет программисту реализовывать код с использованием указателей, вы вряд ли когда-либо столкнётесь с ними. Однако существуют ситуации, когда даже в MQL5 создаются и используются указатели. А поскольку на практике указатели в MQL5 отличаются от указателей в C и C++, вы практически никогда о них здесь не услышите.
Поэтому прежде, чем вы поймёте, что мы собираемся делать, мне нужно, чтобы вы уяснили: любая функция или процедура находится в памяти по определённому адресу. Данный адрес хранится в переменной особого типа, известной как указатель. Указатели — это, безусловно, самый мощный инструмент в программировании. Но в то же время они и самые запутанные, и самые сложные для освоения, учитывая, что, манипулируя указателями, мы работаем с содержимым памяти компьютера независимо от типа данных, к которым мы обращаемся.
Что ж, мы не будем слишком углубляться в этот вопрос, именно потому что в этом нет необходимости. Нужно, чтобы вы поняли следующее:
Указатель — это переменная. Данная переменная указывает на область памяти. Если эта область содержит исполняемый код, мы можем передать выполнение программе в эту область.
Просто понимать это уже достаточно для наших нужд и того, что мы собираемся сделать. Отлично, теперь мы знаем, что такое указатель, но как же тогда создать, или, вернее, определить указатель? Это написано на языке MQL5. Итак, наступил именно тот момент, когда нужно понимание того, как работают шаблоны. В статьях От начального до среднего уровня: Шаблон и Typename (V) было объяснено, как работают шаблоны и как с ними работать для создания перегруженных функций и процедур. Но именно здесь всё становится запутанным для новичков: мы также можем определять шаблоны типов данных. Указатель это именно то, что он собой представляет: шаблон типа данных. Однако в этом случае это тип данных, представляющий собой шаблон функции или процедуры. "Ого, теперь всё стало очень сложно, потому что, на мой взгляд, это совершенно бессмысленно".
Не беспокойтесь, давайте не будем спешить. Я понимаю, что в сначала это действительно сложно. Именно поэтому я упомянул этот ресурс только сейчас и буду объяснять его. Необходимо владеть несколькими различными концепциями, чтобы по-настоящему понять, как работать с этим ресурсом, известным как указатель.
В коде 05 функции в строках 04 и 09 были объявлены таким образом намеренно. Это делается для того, чтобы облегчить понимание концепции шаблона, которая будет использоваться при определении указателя. Если присмотреться, то можно заметить, что меняется, по сути, только название функции. Содержание или логика, реализующая функцию, не имеют значения, важно именно объявление. Поскольку объявления там очень похожи как по типу возвращаемого значения, так и по количеству и типу передаваемых в функции аргументов, у нас есть подходящие условия для определения указателя. Для этого добавим следующую строку из приведенного ниже коде. Мы будем постепенно показывать, как меняется код 05, чтобы вы могли по-настоящему понять, что именно происходит.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. typedef string (*FnPtr)(const int); 05. //+------------------------------------------------------------------+ 06. string Msg_01(const int value) 07. { 08. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 09. } 10. //+------------------------------------------------------------------+ 11. string Msg_02(const int value) 12. { 13. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 14. } 15. //+------------------------------------------------------------------+ 16. void OnStart(void) 17. { 18. Print(Msg_01(171)); 19. Print(Msg_02(-375)); 20. } 21. //+------------------------------------------------------------------+
Код 06
А теперь отложите всё, что вы делаете и что может вас отвлекать, и предельно внимательно слушайте то, что я собираюсь объяснить. Если вы не поймёте это начало, вы окажетесь в полной растерянности. Прошу заметить, что в коде 06 мы добавили строку 04, которой не было в коде 05. Именно строка 04 определяет указатель, который мы будем использовать.
Обратите внимание, что это утверждение очень похоже на то, что содержится в строках 06 и 11. За исключением того, что имя переменной было упущено, а имя функции заменено на (FnPtr). Данная часть, этот элемент (FnPtr), может быть любым, который вы захотите использовать. Однако будьте осторожны при этом, поскольку именно данную часть нам вскоре понадобится использовать.
Хорошо, первая часть объяснена. Теперь мы объявим массив с типом, определенным в строке 04 из кода 06. Помните, что там был определен указатель. Вторая часть показана в следующем коде.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. typedef string (*FnPtr)(const int); 05. //+------------------------------------------------------------------+ 06. string Msg_01(const int value) 07. { 08. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 09. } 10. //+------------------------------------------------------------------+ 11. string Msg_02(const int value) 12. { 13. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 14. } 15. //+------------------------------------------------------------------+ 16. void OnStart(void) 17. { 18. FnPtr FnMsg[2]; 19. 20. Print(Msg_01(171)); 21. Print(Msg_02(-375)); 22. } 23. //+------------------------------------------------------------------+
Код 07
Теперь, в строке 18, которую видно в этом коде 07, у нас есть определение массива для использования указателей того типа, который был определен в строке 04. Для упрощения задачи мы определяем статический массив с двумя элементами. Если у вас есть сомнения по поводу того, что здесь происходит, ознакомьтесь с предыдущими статьями, я не собираюсь вдаваться в подробности того, что уже объяснялось ранее. Отлично, мне кажется, вы пока всё правильно понимаете. Следующий шаг можно увидеть в приведенном ниже коде.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. typedef string (*FnPtr)(const int); 05. //+------------------------------------------------------------------+ 06. string Msg_01(const int value) 07. { 08. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 09. } 10. //+------------------------------------------------------------------+ 11. string Msg_02(const int value) 12. { 13. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 14. } 15. //+------------------------------------------------------------------+ 16. void OnStart(void) 17. { 18. FnPtr FnMsg[2]; 19. 20. FnMsg[0] = Msg_01; 21. FnMsg[1] = Msg_02; 22. 23. Print(Msg_01(171)); 24. Print(Msg_02(-375)); 25. } 26. //+------------------------------------------------------------------+
Код 08
"Ух ты, что за безумие? Вы совсем сошли с ума? Код 08 не скомпилируется".
Но, конечно же, код 08 скомпилируется, уважаемый читатель. Как это возможно? Разве вы не поняли, что здесь было сделано? Здесь мы определяем элементы массива. Если это показалось вам совершенно безумным, вернитесь к началу этой темы и попытайтесь понять то, что было сказано. В строке 04 мы определяем тип данных. В строке 18 мы определяем переменную, которая будет использовать тот же тип данных, что и в строке 04. А в строках 20 и 21 мы присваиваем значения элементам переменной, объявленной в строке 18. Больше ничего, всё очень просто и понятно. Самая сложная часть показана в следующем шаге. Посмотрите на это в представленном ниже коде.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. typedef string (*FnPtr)(const int); 05. //+------------------------------------------------------------------+ 06. string Msg_01(const int value) 07. { 08. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 09. } 10. //+------------------------------------------------------------------+ 11. string Msg_02(const int value) 12. { 13. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 14. } 15. //+------------------------------------------------------------------+ 16. void OnStart(void) 17. { 18. FnPtr FnMsg[2]; 19. 20. FnMsg[0] = Msg_01; 21. FnMsg[1] = Msg_02; 22. 23. Print(Msg_01(171)); 24. Print(Msg_02(-375)); 25. 26. for (uchar c = 0, i = 15; c < FnMsg.Size(); c++, i += 30) 27. Print(FnMsg[c](i)); 28. } 29. //+------------------------------------------------------------------+
Код 09
При выполнении кода 09 в терминале появятся следующие сообщения.

Изображение 02
"Что это за безумие? Что тут творится?" Что ж, уважаемый читатель, на этом этапе мы подошли к действительно сложной части. В каком-то смысле, я даже немного переборщил с этим кодом. Потому что, если вы пропустите предыдущие шаги и попытаетесь разобраться с кодом 09 с самого начала, вы, скорее всего, запутаетесь ещё больше, чем когда-либо в своей жизни. Это связано с тем, что вы смогли бы понять первые две строки, которые видны на этом изображении 02, но откуда берутся две других, на которые я обращаю внимание? И именно в этом вся суть. И всё же в MQL5 указатели на самом деле очень просты для объяснения и понимания. Но даже в этом случае они могут сбить с толку того, кто видит использующий их код, не понимая до конца, что именно там реализовано.
Прошу заметить, что две выделенные строки на изображении 02 точно определены в строке 27 кода 09. Но как? Вот тут-то и возникает путаница. Поскольку в объявлении в строке 18 используется тип, являющийся указателем, при виде строки 27 вы ожидали бы получить определённый тип информации. Однако компилятор понял, что перед нами вызов функции. Вот почему это объявление сформулировано именно так. И именно поэтому результат такой, как можно увидеть на изображении 02. Прошу заметить, что название функции не имеет значения. Строка 27 позволит точно определить, какую функцию нужно вызвать, поскольку в строках 20 и 21 мы присвоили функцию элементу массива.
Возможно, вы думаете: "Но зачем создавать такие сложности? Разве в жизни и так их не достаточно? Неужели нам действительно нужно было что-то подобное, при том что это вообще технически возможно? Господи! Программисты странные люди, они все совершенно сумасшедшие".
Возможно, значительная часть программистов в какой-то момент действительно сойдёт с ума и превратится в очень странных людей. (СМЕХ). Но в целом, мы все очень добрые люди, просто немного эксцентричные. Однако на данном этапе мы уже можем задуматься о том, что раньше было невозможно сделать. Это потому, что вы ещё не видели показанного в этой теме. Но теперь мы обновим код 04, увиденный в предыдущей теме, чтобы использовать то, что мы видели в этой теме. Таким образом, мы получим этот код:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_KEY_UP 38 05. #define def_KEY_DOWN 40 06. //+----------------+ 07. #define macro_NameObject "Demo" + (string)ObjectsTotal(0) 08. //+------------------------------------------------------------------+ 09. string gl_Objs[2]; 10. //+------------------------------------------------------------------+ 11. typedef void (*ProcPtr)(void); 12. typedef void (*KeyEvent)(int &); 13. //+------------------------------------------------------------------+ 14. void OnStart(void) 15. { 16. ProcPtr proc[3]; 17. 18. proc[0] = Init; 19. proc[1] = Loop; 20. proc[2] = Deinit; 21. 22. for (uint c = 0; c < proc.Size(); c++) 23. proc[c](); 24. } 25. //+------------------------------------------------------------------+ 26. void Init(void) 27. { 28. ObjectCreate(0, gl_Objs[0] = macro_NameObject, OBJ_VLINE, 0, 0, 0); 29. ObjectSetInteger(0, gl_Objs[0], OBJPROP_COLOR, clrRoyalBlue); 30. ObjectCreate(0, gl_Objs[1] = macro_NameObject, OBJ_HLINE, 0, 0, 0); 31. ObjectSetInteger(0, gl_Objs[1], OBJPROP_COLOR, clrPurple); 32. } 33. //+------------------------------------------------------------------+ 34. void Loop(void) 35. { 36. KeyEvent key[3]; 37. int pos = 0; 38. 39. key[0] = Bar_NONE; 40. key[1] = Bar_Next; 41. key[2] = Bar_Prev; 42. 43. while (!IsStopped()) 44. { 45. key[TerminalInfoInteger(TERMINAL_KEYSTATE_UP) ? 1 : (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN) ? 2 : 0)](pos); 46. Sleep(100); 47. } 48. } 49. //+------------------------------------------------------------------+ 50. void Event(const int arg) 51. { 52. MqlRates rate[1]; 53. 54. Comment(StringFormat("Current bar analyzed: %d", arg)); 55. CopyRates(_Symbol, _Period, arg, rate.Size(), rate); 56. for (uint c = 0; c < gl_Objs.Size(); c++) 57. ObjectMove(0, gl_Objs[c], 0, rate[0].time, rate[0].close); 58. } 59. //+------------------------------------------------------------------+ 60. void Deinit(void) 61. { 62. Comment(""); 63. for (uint c = 0; c < gl_Objs.Size(); c++) 64. ObjectDelete(0, gl_Objs[c]); 65. ChartRedraw(); 66. } 67. //+------------------------------------------------------------------+ 68. void Bar_Prev(int &arg) 69. { 70. arg = (arg < Bars(_Symbol, _Period) ? arg + 1 : arg); 71. Event(arg); 72. } 73. //+------------------------------------------------------------------+ 74. void Bar_Next(int &arg) 75. { 76. arg = (arg > 0 ? arg - 1 : arg); 77. Event(arg); 78. } 79. //+------------------------------------------------------------------+ 80. void Bar_NONE(int &arg) 81. {} 82. //+------------------------------------------------------------------+
Код 10
Результат выполнения кода 10 будет точно таким же, как показано в анимации 01. Поэтому мы не будем повторять это здесь. Однако в коде 10 я позволяю себе небольшую вольность. Я делаю это для того, чтобы вы поняли, что нам не всегда нужно делать что-то только одним способом. Существует множество способов добиться одного и того же результата. Некоторые из них проще, но немного замедляют процесс расширения кода, в то время как другие, несколько более сложные, позволяют расширять его очень быстро.
В любом случае, на мой взгляд, код 10 не содержит элементов, которые были бы настолько сложны для понимания. Тем более, что в данный момент материал ещё достаточно свеж в памяти. Однако в коде 10 есть некоторые моменты, которые в представленном виде могут показаться не совсем логичными. Давайте коротко объясним эти моменты.
Первый из них находится в процедуре OnStart. Здесь мы видим, что в строке 16 объявлен массив процедур. Сразу после этого, в строках 18, 19 и 20, мы определяем процедуры, которые будут выполняться для каждого элемента массива. Далее мы входим в цикл для выполнения каждой из данных процедур в определенном порядке. Я понимаю, что на первый взгляд это может показаться нелогичным, но вам необходимо понимать, что цель этих статей — научить вас мыслить как программист.
Теперь представьте себе последовательность небольших, постоянно повторяющихся шагов, которые необходимо выполнить в определенной последовательности. Если бы вам пришлось прописывать строку за строкой каждую из процедур, которые необходимо выполнить, это не только заняло бы много времени, но и превратило бы изменение или исправление порядка выполнения в нечто утомительное. Но если для вычисления индексов в массиве можно использовать математическое выражение, то вполне можно определить процедуры как элементы того же самого массива. Сделав это, вы могли бы использовать цикл для выполнения элементов массива в определённом порядке, что в противном случае было бы невозможно.
Что касается процедуры Loop, которая находится в строке 34, то она имеет очень схожее назначение. Однако там механизм работает несколько иначе. В этом случае, между строками 39 и 41, мы определяем некоторые процедуры, которые будут использоваться в зависимости от нажатой клавиши. А в строке 45, используя тернарный оператор, мы вызываем соответствующую процедуру.
Такой подход или такой способ мышления при решении определённой проблемы может иногда превратить задачу, которая была бы очень утомительной, в нечто гораздо более простое и быстрое. Потому что, если бы потребовалось что-то изменить, нам пришлось бы внести минимальное количество изменений и это позволило бы очень быстро реализовать любое решение.
Заключительные идеи
Хотя мы можем сделать гораздо больше показанного здесь, я уверен, что для многих то, что мы рассмотрели в этой статье, является чем-то совершенно новым и, следовательно, должно быть изучено должным образом. Нужно понять каждый пункт, изложенный в этой статье.
В приложении вы найдете основные коды, представленные в этой статье. Поэтому воспользуйтесь этим временем, чтобы отработать на практике всё то, что было здесь показано. Кроме того, постарайтесь поразмышлять над тем, что было объяснено в предыдущих статьях, и над тем, как те же самые факты и концепции могут быть использованы вместе с тем, что мы увидели здесь. Постарайтесь реализовать что-то, что позволит использовать все знания, полученные к этому моменту. А в следующей статье мы снова поговорим о событиях, связанных с объектами. Но на этот раз основной темой будет использование мыши, так как часть, связанная с клавиатурой, уже была изучена должным образом.
Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/15995
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
Статистический арбитраж с использованием коинтегрированных акций (Часть 4): Обновление параметров модели в реальном времени
Торговые инструменты MQL5 (Часть 21): Добавление темы в стиле киберпанк в графики регрессии
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования