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

От начального до среднего уровня: Наследование

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

Введение

В предыдущей статье, « От начального до среднего уровня: Struct (VII)» мы начали работу со структурной программой, которая, хотя при первом знакомстве может показаться довольно сложной, в итоге оказывается очень увлекательной и интересной. Я знаю это, потому что в начале своего пути мне было очень трудно понять, почему одни коды работают, а другие нет. После многих лет борьбы и упорства я наконец-то усвоил те концепции, которые пытаюсь донести до вас в этих статьях. Многие люди считают, что научиться программировать можно, пройдя курс или просто изучая код других программистов.

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

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


Простое наследование

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

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

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

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

Как правило, большая часть кода, ориентированного на работу на финансовом рынке, для чего используются MetaTrader 5 и, следовательно, MQL5, не нуждается в том уровне абстракции, который я представляю в этой статье. Для нашей цели также не обязательно использовать структурное или объектно-ориентированное программирование.

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

Повторюсь, для создания советника или индикатора не обязательно разбираться в структурном программировании, поскольку это можно сделать с помощью традиционного программирования. Если вы сомневаетесь в моих словах, можете ознакомьтесь с моей первой статьей: «Разработка торгового советника с нуля». В ней, без использования каких-либо структурных или объектно-ориентированных методов программирования, мы рассмотрели, как создать простой советник, позволяющий отправлять ордеры непосредственно на график.

То же самое относится и к индикаторам. В статье «От начального до среднего уровня: Индикатор (IV)» мы показали, как отобразить индикатор на графике. Всё это просто и не требует особых знаний. Для создания нужного приложения потребуется лишь здравый смысл и понимание документации MQL5.

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

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. struct st_Reg
005. {
006.     //+----------------+
007.     private:
008.     //+----------------+
009.         string  h_value;
010.         uint    k_value;
011.     //+----------------+
012.     public:
013.     //+----------------+
014.         void Set(const uint arg1, const string arg2)
015.         {
016.             k_value = arg1;
017.             h_value = arg2;
018.         }
019.     //+----------------+
020.         uint Get_K(void)    { return k_value; }
021.     //+----------------+
022.         string Get_H(void)  { return h_value; }
023.     //+----------------+
024. };
025. //+------------------------------------------------------------------+
026. struct st_Bio
027. {
028.     //+----------------+
029.     private:
030.     //+----------------+
031.         string  h_value;
032.         string  b_value;
033.         uint    k_value;
034.     //+----------------+
035.     public:
036.     //+----------------+
037.         void Set(const uint arg1, const string arg2, const string arg3)
038.         {
039.             k_value = arg1;
040.             h_value = arg2;
041.             b_value = arg3;
042.         }
043.     //+----------------+
044.         uint Get_K(void)    { return k_value; }
045.     //+----------------+
046.         bool Get_Bio(string &arg1, string &arg2)
047.         {
048.             arg1 = h_value;
049.             arg2 = b_value;
050. 
051.             return true;
052.         }
053.     //+----------------+
054. };
055. //+------------------------------------------------------------------+
056. template <typename T>
057. struct st_Data
058. {
059.     //+----------------+
060.     private:
061.     //+----------------+
062.         T Values[];
063.     //+----------------+
064.     public:
065.     //+----------------+
066.         bool Set(const T &arg)
067.         {
068.             if (ArrayResize(Values, Values.Size() + (Values.Size() == 0 ? 2 : 1)) == INVALID_HANDLE)
069.                 return false;
070. 
071.             Values[Values.Size() - 1] = arg;
072. 
073.             return true;
074.         }
075.     //+----------------+
076.         T Get(const uint index)
077.         {
078.             for (uint c = 0; c < Values.Size(); c++)
079.                 if (Values[c].Get_K() == index)
080.                     return Values[c];
081. 
082.             return Values[0];
083.         }
084.     //+----------------+
085. };
086. //+------------------------------------------------------------------+
087. #define PrintX(X) Print(#X, " => [", X, "]")
088. //+------------------------------------------------------------------+
089. void CheckBio(st_Data <st_Bio> &arg)
090. {
091.     string sz[2];
092. 
093.     Print("Checking data in the structure...");
094.     for (uint i = 7; i < 11; i += 3)
095.     {
096.         Print("Index: ", i, " Result: ");
097.         if (arg.Get(i).Get_Bio(sz[0], sz[1]))
098.             ArrayPrint(sz);
099.         else
100.             Print("Failed.");
101.     }
102. }
103. //+------------------------------------------------------------------+
104. void OnStart(void)
105. {
106.     const string T = "possible loss of data due to type conversion";
107.     const string M[] = {"2", "cool", "4", "zero", "mad", "five", "what", "xoxo"};
108.     const uint   K[] = {2, 1, 4, 0, 7, 5, 3, 6};
109. 
110.     st_Data <st_Reg> Info_1;
111.     st_Data <st_Bio> Info_2;
112. 
113.     string  H[];
114. 
115.     StringSplit(T, ' ', H);
116.     for (uint c = 0; c < H.Size(); c++)
117.     {
118.         st_Reg  reg;
119.         st_Bio  bio;
120. 
121.         reg.Set(K[c], H[c]);
122.         bio.Set(K[c], M[c], H[c]);
123. 
124.         Info_1.Set(reg);
125.         Info_2.Set(bio);
126.     }
127. 
128.     PrintX(Info_1.Get(3).Get_H());
129.     PrintX(Info_1.Get(13).Get_H());
130.     CheckBio(Info_2);
131. }
132. //+------------------------------------------------------------------+

Код 01

При выполнении кода 01 мы получим результат, показанный на изображении ниже.

Изображение 01

Теперь перейдём к сути. Подобно тому, как у животных и растений есть общие элементы, в коде 01 k_value является общим элементом для st_Reg и st_Bio. Однако данные структуры не взаимодействуют друг с другом и являются совершенно разными. Визуально это показано ниже.

Изображение 02

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

Чтобы это стало понятнее, представьте, что вы по какой-либо причине решили улучшить функцию Get_K, присутствующую в структуре st_Reg. Таким образом, вы замечаете, что общий код значительно улучшился, но в st_Bio вы этого не делаете. При одновременном использовании обеих структур заметно, что st_Bio, несмотря на использование Get_K, ведет себя иначе, чем st_Reg.

И именно здесь начинаются проблемы. Код Get_K, присутствующий в структуре st_Reg, копируется в структуру st_Bio, и через некоторое время код Get_K необходимо снова изменить, что подразумевает выполнение данной операции в обеих структурах.

Помимо того, что это огромная трата времени, подобные действия очень затрудняют улучшение кода. И здесь мы используем только две структуры. В реальном коде могут быть десятки или даже сотни структур, имеющих нечто общее. Можете представить, каково это — отлаживать и поддерживать такой код? Адская работа. Однако существует простой способ упростить всю эту работу. Данный метод заключается именно в использовании наследования.

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

Давайте теперь применим это на практике, заставив код 01 использовать наследование. Для достижения нашей цели первый шаг показываем в приведенном ниже фрагменте кода.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Base
05. {
06.     private:
07.         uint KeyValue;
08.     public:
09.     //+----------------+
10.         void SetKey(const uint arg) { KeyValue = arg; }
11.     //+----------------+
12.         uint GetKey(void) { return KeyValue; }
13. };
14. //+------------------------------------------------------------------+
15. struct st_Reg
16. {
17.     private:
18.         string  Value;
19.     public:
20.     //+----------------+
21.         void SetValue(const uint arg1, const string arg2)
22.         {
23.             Value = arg2;
24.         }
25.     //+----------------+
26.         string GetValue(void)  { return Value; }
27. };
28. //+------------------------------------------------------------------+
29. struct st_Bio
30. {
31.     private:
32.         string  Value[2];
33.     public:
34.     //+----------------+
35.         void SetValue(const uint arg1, const string arg2, const string arg3)
36.         {
37.             Value[0] = arg2;
38.             Value[1] = arg3;
39.         }
40.     //+----------------+
41.         bool GetValue(string &arg1, string &arg2)
42.         {
43.             arg1 = Value[0];
44.             arg2 = Value[1];
45. 
46.             return true;
47.         }
48.     //+----------------+
49. };
50. //+------------------------------------------------------------------+
                   .
                   .
                   .

Код 02

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

Изображение 03

Прошу заметить, что оно очень похоже на изображение 02. Но в изображении 03 не используется наследование. Это связано с тем, что фрагмент 02 его тоже не использует. Поэтому, если мы попытаемся скомпилировать код 01, используя фрагмент кода 02, компилятор выдаст несколько ошибок. Чтобы это исправить, нам нужно преобразовать изображение 03 в изображение 04, как показано ниже.

Изображение 04

На изображении 04 стрелки указывают, как будет осуществляться наследование. Иными словами, мы собираемся включить содержимое структуры st_Base в структуры st_Bio и st_Reg, не внося в код существенных изменений. Для этого просто изменим код 02 на код, показанный ниже.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Base
05. {
06.     private:
07.         uint KeyValue;
08.     public:
09.     //+----------------+
10.         void SetKey(const uint arg) { KeyValue = arg; }
11.     //+----------------+
12.         uint GetKey(void) { return KeyValue; }
13. };
14. //+------------------------------------------------------------------+
15. struct st_Reg : public st_Base
16. {
17.     private:
18.         string  Value;
19.     public:
20.     //+----------------+
21.         void SetValue(const uint arg1, const string arg2)
22.         {
23.             Value = arg2;
24.         }
25.     //+----------------+
26.         string GetValue(void)  { return Value; }
27. };
28. //+------------------------------------------------------------------+
29. struct st_Bio : public st_Base
30. {
31.     private:
32.         string  Value[2];
33.     public:
34.     //+----------------+
35.         void SetValue(const uint arg1, const string arg2, const string arg3)
36.         {
37.             Value[0] = arg2;
38.             Value[1] = arg3;
39.         }
40.     //+----------------+
41.         bool GetValue(string &arg1, string &arg2)
42.         {
43.             arg1 = Value[0];
44.             arg2 = Value[1];
45. 
46.             return true;
47.         }
48.     //+----------------+
49. };
50. //+------------------------------------------------------------------+
                   .
                   .
                   .

Код 03

Посмотрите, как сложно было реализовать наследование в коде. Однако, как можно видеть, во фрагменте кода 03 присутствуют ссылки на значения, которые ещё не были разрешены. Это связано с тем, что в этом фрагменте мы реализовали только наследование. Мы пока не используем его на практике таким образом, чтобы применять информацию о KeyValue. Наша цель - позволить st_Data установить связь между данными для построения того, что можно увидеть на изображении 01.

Для устранения подобных проблем нам необходимо снова изменить код. В результате это будет выглядеть так:

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. struct st_Base
005. {
006.     private:
007.         uint KeyValue;
008.     public:
009.     //+----------------+
010.         void SetKey(const uint arg) { KeyValue = arg; }
011.     //+----------------+
012.         uint GetKey(void) { return KeyValue; }
013. };
014. //+------------------------------------------------------------------+
015. struct st_Reg : public st_Base
016. {
017.     private:
018.         string  Value;
019.     public:
020.     //+----------------+
021.         void SetValue(const uint arg1, const string arg2)
022.         {
023.             SetKey(arg1);
024.             Value = arg2;
025.         }
026.     //+----------------+
027.         string GetValue(void)  { return Value; }
028. };
029. //+------------------------------------------------------------------+
030. struct st_Bio : public st_Base
031. {
032.     private:
033.         string  Value[2];
034.     public:
035.     //+----------------+
036.         void SetValue(const uint arg1, const string arg2, const string arg3)
037.         {
038.             SetKey(arg1);
039.             Value[0] = arg2;
040.             Value[1] = arg3;
041.         }
042.     //+----------------+
043.         bool GetValue(string &arg1, string &arg2)
044.         {
045.             arg1 = Value[0];
046.             arg2 = Value[1];
047. 
048.             return true;
049.         }
050.     //+----------------+
051. };
052. //+------------------------------------------------------------------+
053. template <typename T>
054. struct st_Data
055. {
056.     private:
057.         T Values[];
058.     public:
059.     //+----------------+
060.         bool Set(const T &arg)
061.         {
062.             if (ArrayResize(Values, Values.Size() + (Values.Size() == 0 ? 2 : 1)) == INVALID_HANDLE)
063.                 return false;
064. 
065.             Values[Values.Size() - 1] = arg;
066. 
067.             return true;
068.         }
069.     //+----------------+
070.         T Get(const uint index)
071.         {
072.             for (uint c = 0; c < Values.Size(); c++)
073.                 if (Values[c].GetKey() == index)
074.                     return Values[c];
075. 
076.             return Values[0];
077.         }
078. };
079. //+------------------------------------------------------------------+
080. #define PrintX(X) Print(#X, " => [", X, "]")
081. //+------------------------------------------------------------------+
082. void CheckBio(st_Data <st_Bio> &arg)
083. {
084.     string sz[2];
085. 
086.     Print("Checking data in the structure...");
087.     for (uint i = 7; i < 11; i += 3)
088.     {
089.         Print("Index: ", i, " Result: ");
090.         if (arg.Get(i).GetValue(sz[0], sz[1]))
091.             ArrayPrint(sz);
092.         else
093.             Print("Failed.");
094.     }
095. }
096. //+------------------------------------------------------------------+
097. void OnStart(void)
098. {
099.     const string T = "possible loss of data due to type conversion";
100.     const string M[] = {"2", "cool", "4", "zero", "mad", "five", "what", "xoxo"};
101.     const uint   K[] = {2, 1, 4, 0, 7, 5, 3, 6};
102. 
103.     st_Data <st_Reg> Info_1;
104.     st_Data <st_Bio> Info_2;
105. 
106.     string  H[];
107. 
108.     StringSplit(T, ' ', H);
109.     for (uint c = 0; c < H.Size(); c++)
110.     {
111.         st_Reg  reg;
112.         st_Bio  bio;
113. 
114.         reg.SetValue(K[c], H[c]);
115.         bio.SetValue(K[c], M[c], H[c]);
116. 
117.         Info_1.Set(reg);
118.         Info_2.Set(bio);
119.     }
120. 
121.     PrintX(Info_1.Get(3).GetValue());
122.     PrintX(Info_1.Get(13).GetValue());
123.     CheckBio(Info_2);
124. }
125. //+------------------------------------------------------------------+

Код 04

Код 04 имеет тот же эффект, что и код 01. Однако в коде 04 мы используем наследование между структурами. Таким образом, любое улучшение, внесенное нами в структуру st_Base, автоматически принесет пользу всему коду. Иными словами, любое изменение, каким бы незначительным оно ни было, внесенное в структуру st_Base, будет автоматически доступно для st_Reg и st_Bio.

Разумеется, при условии, что процедура, функция или переменная включены в публичной части кода. Если в публичной части структуры st_Base вносятся изменения, ни st_Reg, ни st_Bio не будут знать об этом изменении, и оно будет ограничено классом st_Base.

Один момент, который мы не упомянули в коде 04, но который может быть важен, — это то, что в строках 15 и 30 наследование осуществляется публично. Это позволяет любой переменной типа одной из двух структур также иметь доступ к любому общедоступному элементу структуры st_Base. Если бы вместо зарезервированных слов public, которые можно увидеть в упомянутых строках, мы использовали бы слово private, то любая переменная, использующая данную структуру, будь то st_Bio или st_Reg, не имела бы доступа к содержимому структуры st_Base. Предлагаю вам попробовать позже сделать это с файлом, который будет в приложении, чтобы лучше понять.

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

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


Структуры, шаблоны и наследование: комбинация шума

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

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

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

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

Итак, наш код будет начинаться так:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Base
05. {
06.     private :
07.         uint KeyValue;
08.     public  :
09.     //+----------------+
10.         void SetKey(const uint arg)
11.         {
12.             KeyValue = arg;
13.         }
14.     //+----------------+
15.         uint GetKey(void)
16.         {
17.             return KeyValue;
18.         }
19.     //+----------------+
20. };
21. //+------------------------------------------------------------------+

Код 05

Хорошо, я думаю, все поймут код 05. Затем мы сможем перейти к следующему этапу.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Base
05. {
06.     private :
07.         uint KeyValue;
08.     public  :
09.     //+----------------+
10.         void SetKey(const uint arg)
11.         {
12.             KeyValue = arg;
13.         }
14.     //+----------------+
15.         uint GetKey(void)
16.         {
17.             return KeyValue;
18.         }
19.     //+----------------+
20. };
21. //+------------------------------------------------------------------+
22. struct st_Dev01 : public st_Base
23. {
24.     private :
25.         string ValueInfo;
26.     public  :
27.     //+----------------+
28.         void SetKeyInfo(uint arg1, string arg2)
29.         {
30.             SetKey(arg1);
31.             ValueInfo = arg2;
32.         }
33.     //+----------------+
34.         string GetInfo(void)
35.         {
36.             return ValueInfo;
37.         }
38.     //+----------------+
39. }
40. //+------------------------------------------------------------------+

Код 06

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Base
05. {
06.     private :
07.         uint KeyValue;
08.     public  :
09.     //+----------------+
10.         void SetKey(const uint arg)
11.         {
12.             KeyValue = arg;
13.         }
14.     //+----------------+
15.         uint GetKey(void)
16.         {
17.             return KeyValue;
18.         }
19.     //+----------------+
20. };
21. //+------------------------------------------------------------------+
22. struct st_Dev01 : public st_Base
23. {
24.     private :
25.         string ValueInfo;
26.     public  :
27.     //+----------------+
28.         void SetKeyInfo(uint arg1, string arg2)
29.         {
30.             SetKey(arg1);
31.             ValueInfo = arg2;
32.         }
33.     //+----------------+
34.         string GetInfo(void)
35.         {
36.             return ValueInfo;
37.         }
38.     //+----------------+
39. };
40. //+------------------------------------------------------------------+
41. struct st_List
42. {
43.     private :
44.         st_Dev01    List[];
45.         uint        nElements;
46.     public  :
47.     //+----------------+
48.         void Clear(void)
49.         {
50.             ArrayFree(List);
51.             nElements = 0;
52.         }
53.     //+----------------+
54.         bool AddList(const st_Dev01 &arg)
55.         {
56.             nElements += (nElements == 0 ? 2 : 1);
57.             ArrayResize(List, nElements);
58.             List[nElements - 1] = arg;
59. 
60.             return true;
61. 
62.         }
63.     //+----------------+
64.         st_Dev01 SearchKey(const uint arg)
65.         {
66.             for (uint c = 1; c < nElements; c++)
67.                 if (List[c].GetKey() == arg)
68.                     return List[c];
69. 
70.             return List[0];
71.         }
72.     //+----------------+
73. };
74. //+------------------------------------------------------------------+

Код 07

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "Tutorial\File 01.mqh"
05. //+------------------------------------------------------------------+
06. #define PrintX(X) Print(#X, " => [", X, "]")
07. //+------------------------------------------------------------------+
08. void OnStart(void)
09. {
10.     const string Names[] = 
11.         {
12.             "Daniel Jose",
13.             "Edimarcos Alcantra",
14.             "Carlos Almeida",
15.             "Yara Alves"
16.         };
17. 
18.     st_List list;
19. 
20.     for (uint c = 0; c < Names.Size(); c++)
21.     {
22.         st_Dev01 info;
23. 
24.         info.SetKeyInfo(c, Names[c]);
25.         list.AddList(info);
26.     }
27. 
28.     PrintX(list.SearchKey(2).GetInfo());
29. }
30. //+------------------------------------------------------------------+

Код 08

Прошу заметить, что весь код из кода 07 на самом деле представлял собой заголовочный файл. Он добавляется к коду 08 в строке 04. В строке 10 мы создали небольшой массив, который содержит несколько имен. В строке 18 представлена структура данных. В строке 25 мы создали саму структуру. В строке 28, используя ключ, мы ищем информацию, которая относится к данному конкретному ключу. В результате выполнения кода мы получаем то, что показано ниже.

Изображение 05

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Base
05. {
06.     private :
07.         uint KeyValue;
08.     public  :
09.     //+----------------+
10.         void SetKey(const uint arg)
11.         {
12.             KeyValue = arg;
13.         }
14.     //+----------------+
15.         uint GetKey(void)
16.         {
17.             return KeyValue;
18.         }
19.     //+----------------+
20. };
21. //+------------------------------------------------------------------+
22. struct st_Dev01 : public st_Base
23. {
24.     private :
25.         string ValueInfo;
26.     public  :
27.     //+----------------+
28.         void SetKeyInfo(uint arg1, string arg2)
29.         {
30.             SetKey(arg1);
31.             ValueInfo = arg2;
32.         }
33.     //+----------------+
34.         string GetInfo(void)
35.         {
36.             return ValueInfo;
37.         }
38.     //+----------------+
39. };
40. //+------------------------------------------------------------------+
41. template <typename T>
42. struct st_List
43. {
44.     private :
45.         T    List[];
46.         uint nElements;
47.     public  :
48.     //+----------------+
49.         void Clear(void)
50.         {
51.             ArrayFree(List);
52.             nElements = 0;
53.         }
54.     //+----------------+
55.         bool AddList(const T &arg)
56.         {
57.             nElements += (nElements == 0 ? 2 : 1);
58.             ArrayResize(List, nElements);
59.             List[nElements - 1] = arg;
60. 
61.             return true;
62. 
63.         }
64.     //+----------------+
65.         T SearchKey(const uint arg)
66.         {
67.             for (uint c = 1; c < nElements; c++)
68.                 if (List[c].GetKey() == arg)
69.                     return List[c];
70. 
71.             return List[0];
72.         }
73.     //+----------------+
74. };
75. //+------------------------------------------------------------------+

Код 09

А для того, чтобы код 08 продолжал работать, нам теперь нужно обновить его до того вида, который можно увидеть ниже.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "Tutorial\File 01.mqh"
05. //+------------------------------------------------------------------+
06. #define PrintX(X) Print(#X, " => [", X, "]")
07. //+------------------------------------------------------------------+
08. void OnStart(void)
09. {
10.     const string Names[] = 
11.         {
12.             "Daniel Jose",
13.             "Edimarcos Alcantra",
14.             "Carlos Almeida",
15.             "Yara Alves"
16.         };
17. 
18.     st_List <st_Dev01> list;
19. 
20.     for (uint c = 0; c < Names.Size(); c++)
21.     {
22.         st_Dev01 info;
23.         
24.         info.SetKeyInfo(c, Names[c]);
25.         list.AddList(info);
26.     }
27. 
28.     PrintX(list.SearchKey(2).GetInfo());
29. }
30. //+------------------------------------------------------------------+

Код 10

Прошу заметить, что в коде 10 мы изменили только строку 18, сравнив её с тем, что было в исходном коде 08.

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

Во-первых, заголовочный файл. Это теперь видно в приведенном ниже коде.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. template <typename KEY>
05. struct st_Base
06. {
07.     private :
08.         KEY KeyValue;
09.     public  :
10.     //+----------------+
11.         void SetKey(const KEY arg)
12.         {
13.             KeyValue = arg;
14.         }
15.     //+----------------+
16.         KEY GetKey(void)
17.         {
18.             return KeyValue;
19.         }
20.     //+----------------+
21. };
22. //+------------------------------------------------------------------+
23. template <typename KEY>
24. struct st_Dev01 : public st_Base <KEY>
25. {
26.     private :
27.         string ValueInfo;
28.     public  :
29.     //+----------------+
30.         void SetKeyInfo(KEY arg1, string arg2)
31.         {
32.             SetKey(arg1);
33.             ValueInfo = arg2;
34.         }
35.     //+----------------+
36.         string GetInfo(void)
37.         {
38.             return ValueInfo;
39.         }
40.     //+----------------+
41. };
42. //+------------------------------------------------------------------+
43. template <typename T, typename KEY>
44. struct st_List
45. {
46.     private :
47.         T    List[];
48.         uint nElements;
49.     public  :
50.     //+----------------+
51.         void Clear(void)
52.         {
53.             ArrayFree(List);
54.             nElements = 0;
55.         }
56.     //+----------------+
57.         bool AddList(const T &arg)
58.         {
59.             nElements += (nElements == 0 ? 2 : 1);
60.             ArrayResize(List, nElements);
61.             List[nElements - 1] = arg;
62. 
63.             return true;
64. 
65.         }
66.     //+----------------+
67.         T SearchKey(const KEY arg)
68.         {
69.             for (uint c = 1; c < nElements; c++)
70.                 if (List[c].GetKey() == arg)
71.                     return List[c];
72. 
73.             return List[0];
74.         }
75.     //+----------------+
76. };
77. //+------------------------------------------------------------------+

Код 11

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

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

Поскольку структура st_Dev01 наследует структуру st_Base, которая является шаблоном, структура st_Dev01 ТАКЖЕ ДОЛЖНА быть определена как шаблонная структура. Итак, не важно, как мы думаем: тот факт, что одна структура данных наследует шаблон структуры данных, обязывает саму структуру, которая наследует его, также быть шаблоном. Это не очень сложно, поскольку всё, что нам нужно сделать, это определить строку 23.

Однако, и вот тут начинается самое сложное, нам нужно переместить данный вид, определенный в строке 23, в базовую структуру. Иными словами, st_Base необходимо получить это значение, которое будет определено позже в коде. Кажется, всё просто, но поверьте, мне потребовалось много времени, чтобы понять, что нужно изменить объявление в строке 22 из кода 09, на объявление в строке 24 из кода 11.

Вы даже не представляете, сколько страданий я пережил, пока не научился этому, поскольку в то время НИКТО не объяснял это. Полагаю, вы всегда представляете, что сначала нужно определить, что изображено в строке 23, а затем определить, что изображено в строке 30. Однако, если определение в строке 24 не будет исправлено, код ВООБЩЕ НИКАК НЕ СКОМПИЛИРУЕТСЯ.

Поскольку наш список будет использовать структуру данных, которая будет перегружена в зависимости от конкретного случая, нам также необходимо изменить код объявления структуры st_List. Но это легко решается, всего лишь нужно изменить строку 43, а затем скорректировать строку 57. Хорошо. Наш заголовочный файл почти готов. Единственное, что по-прежнему связывает это с определенным типом данных, — это объявление в строке 27, но мы это изменим позже. Теперь давайте посмотрим, каким стал основной файл. Его можно увидеть ниже:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "Tutorial\File 01.mqh"
05. //+------------------------------------------------------------------+
06. #define PrintX(X) Print(#X, " => [", X, "]")
07. //+------------------------------------------------------------------+
08. void OnStart(void)
09. {
10.     #define def_TypeKey uint
11. 
12.     const string Names[] = 
13.         {
14.             "Daniel Jose",
15.             "Edimarcos Alcantra",
16.             "Carlos Almeida",
17.             "Yara Alves"
18.         };
19. 
20.     st_List <st_Dev01 <def_TypeKey>, def_TypeKey> list;
21. 
22.     for (uint c = 0; c < Names.Size(); c++)
23.     {
24.         st_Dev01 <def_TypeKey> info;
25. 
26.         info.SetKeyInfo(c, Names[c]);
27.         list.AddList(info);
28.     }
29. 
30.     PrintX(list.SearchKey(2).GetInfo());
31. 
32.     #undef def_TypeKey
33. }
34. //+------------------------------------------------------------------+

Код 12

«Господи! Помилуй всех нас! Этот парень неадекватен. Как он смеет приходить сюда и показывать нам показанный выше код? Вам разве плевать на нас, начинающих программистов? Вы хотите убить нас, просто показав такой безумный код?»

Успокойтесь, уважаемые читатели. Не горячитесь. Код 12 настолько прост, что кажется почти скучным, несмотря на то, что он довольно забавный. Но будьте внимательны. Так как нам необходимо повторять определенные действия в разных местах кода, мы используем директиву в строке 10 для создания определения, которое позволяет быстро и безопасно вносить изменения во всех этих местах. В коде 12 нам нужно изменить всего два момента. Первый момент - это строка 20. Сравните объявление этой переменной с объявлением той же переменной в коде 10.

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

Однако на этом мы ещё не закончили обсуждение этой темы, поскольку у нас ещё есть возможность внести ещё одно улучшение, мы сделаем это сейчас. Это позволит составить почти идеальный список структур. Я говорю «почти», потому что есть небольшая проблема, которую невозможно решить без использования классов, то есть без использования ООП. Но это станет ясно позже, когда мы покажем, в чем заключается проблема и как ООП её решает.

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. template <typename KEY>
05. struct st_Base
06. {
07.     private :
08.         KEY KeyValue;
09.     public  :
10.     //+----------------+
11.         void SetKey(const KEY arg)
12.         {
13.             KeyValue = arg;
14.         }
15.     //+----------------+
16.         KEY GetKey(void)
17.         {
18.             return KeyValue;
19.         }
20.     //+----------------+
21. };
22. //+------------------------------------------------------------------+
23. template <typename KEY, typename INFO>
24. struct st_Dev01 : public st_Base <KEY>
25. {
26.     private :
27.         INFO ValueInfo;
28.     public  :
29.     //+----------------+
30.         void SetKeyInfo(KEY arg1, INFO arg2)
31.         {
32.             SetKey(arg1);
33.             ValueInfo = arg2;
34.         }
35.     //+----------------+
36.         INFO GetInfo(void)
37.         {
38.             return ValueInfo;
39.         }
40.     //+----------------+
41. };
42. //+------------------------------------------------------------------+
43. template <typename T, typename KEY>
44. struct st_List
45. {
46.     private :
47.         T    List[];
48.         uint nElements;
49.     public  :
50.     //+----------------+
51.         void Clear(void)
52.         {
53.             ArrayFree(List);
54.             nElements = 0;
55.         }
56.     //+----------------+
57.         bool AddList(const T &arg)
58.         {
59.             nElements += (nElements == 0 ? 2 : 1);
60.             ArrayResize(List, nElements);
61.             List[nElements - 1] = arg;
62. 
63.             return true;
64. 
65.         }
66.     //+----------------+
67.         T SearchKey(const KEY arg)
68.         {
69.             for (uint c = 1; c < nElements; c++)
70.                 if (List[c].GetKey() == arg)
71.                     return List[c];
72. 
73.             return List[0];
74.         }
75.     //+----------------+
76. };
77. //+------------------------------------------------------------------+

Код 13

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "Tutorial\File 01.mqh"
05. //+------------------------------------------------------------------+
06. #define PrintX(X) Print(#X, " => [", X, "]")
07. //+------------------------------------------------------------------+
08. void OnStart(void)
09. {
10.     #define def_TypeKey     string
11.     #define def_TypeInfo    string
12. 
13.     const string Names[][2] = 
14.         {
15.             "Daniel Jose"       , "Chief Programmer",
16.             "Edimarcos Alcantra", "Programmer",
17.             "Carlos Almeida"    , "Junior Programmer",
18.             "Yara Alves"        , "Accounting"
19.         };
20. 
21.     st_List <st_Dev01 <def_TypeKey, def_TypeInfo>, def_TypeKey> list;
22. 
23.     for (int c = 0; c < ArrayRange(Names, 0); c++)
24.     {
25.         st_Dev01 <def_TypeKey, def_TypeInfo> info;
26. 
27.         info.SetKeyInfo(Names[c][1], Names[c][0]);
28.         list.AddList(info);
29.     }
30. 
31.     PrintX(list.SearchKey("Chief Programmer").GetInfo());
32.     PrintX(list.SearchKey("Guest").GetInfo());
33.     
34.     #undef def_TypeKey
35.     #undef def_TypeInfo
36. }
37. //+------------------------------------------------------------------+

Код 14

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

Изображение 06

Как красиво! Изображение 06 – настоящая находка, поскольку оно показывает, как, казалось бы, сложные вещи могут стать невероятно простыми, если приложить усилия к материалу, показанному в статьях.


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

Без сомнения могу утвердить, что данная статья потребует от вас некоторого времени на разбор материала. Это объясняется тем, что всё показанное здесь изначально ориентировано на ООП. Многие считают маловероятным создание чего-то, рекламирующегося как разновидность программирования, но в действительности основано на принципах полностью структурного программирования.

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

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

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

Прикрепленные файлы |
Anexo.zip (2.52 KB)
Знакомство с языком MQL5 (Часть 34): Освоение API и функции WebRequest в языке MQL5 (VIII) Знакомство с языком MQL5 (Часть 34): Освоение API и функции WebRequest в языке MQL5 (VIII)
В этой статье вы узнаете, как создать панель управления в MetaTrader 5. Мы разберем основы добавления полей ввода, кнопок действий и меток для отображения текста. Используя проектный подход, вы увидите, как настроить панель, в которой пользователи могут вводить сообщения и в итоге отображать ответы API-сервера.
Как подключить LLM к советнику MQL5 через Python-сервер Как подключить LLM к советнику MQL5 через Python-сервер
В статье разобраны три ключевые преграды интеграции LLM с MetaTrader 5: отсутствие прямого доступа, жёсткие rate limits и безопасность API‑ключей при архитектурных ограничениях MQL5. Предложена схема с локальным Python‑сервером как мостом между советником и OpenRouter. Рассматриваются WebSocket и fallback на TCP, хранение ключа на сервере, пакетная обработка нескольких символов и формирование технического промпта. Читатель получит готовую архитектуру, снижающую задержки и издержки.
Моделирование рынка (Часть 20): Первые шаги на SQL (III) Моделирование рынка (Часть 20): Первые шаги на SQL (III)
Хотя мы можем выполнять операции с базой данных, содержащей около 10 записей, но материал усваивается гораздо лучше, когда мы работаем с файлом, который содержит более 15 тысяч записей. То есть, если бы мы попытались создать такое вручную, то эта задача была бы огромной. Однако трудно найти такую базу данных, даже для учебных целей, доступную для скачивания. Но на самом деле нам не нужно к этому прибегать, мы можем использовать MetaTrader 5 для создания базы данных для себя. В сегодняшней статье мы рассмотрим, как это сделать.
Возможности Мастера MQL5, которые вам нужно знать (Часть 68): Использование паттернов TRIX и процентного диапазона Уильямса с сетью косинусного ядра Возможности Мастера MQL5, которые вам нужно знать (Часть 68): Использование паттернов TRIX и процентного диапазона Уильямса с сетью косинусного ядра
В продолжение нашей предыдущей статьи, где мы представили пару индикаторов TRIX и процентного диапазона Уильямса, мы рассмотрим, как эту пару индикаторов можно расширить с помощью машинного обучения. TRIX и процентный диапазон Уильямса представляют собой взаимодополняющую пару, отражающую тренд и уровни поддержки/сопротивления. Наш подход на основе машинного обучения использует сверточную нейронную сеть (convolution neural network), в архитектуре которой задействуется косинусное ядро (cosine kernel) при точной настройке прогнозов этой пары индикаторов. Как обычно, это делается в пользовательском файле класса сигналов (signal class), который взаимодействует с Мастером MQL5 для создания советника.