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

От начального до среднего уровня: Шаблон и Typename (IV)

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

Введение

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

В предыдущей статье, "От начального до среднего уровня: Шаблон и Typename (III)", мы начали тему, которая многим новичкам кажется особенно сложной. Это связано с тем, что многие не поняли концепцию, которая очень важна для MQL5-программистов: концепцию шаблонов. Поскольку я понимаю, что многие читатели знают о программировании очень мало, я стараюсь сделать материал как можно более дидактичным.

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

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

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


Как использовать тип шаблона

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06. //+----------------+
07. #define macro_Swap(X)   for (uchar i = 0, j = sizeof(X) - 1, tmp; i < j; i++, j--)  \
08.                         {                                                           \
09.                             tmp = X.u8_bits[i];                                     \
10.                             X.u8_bits[i] = X.u8_bits[j];                            \
11.                             X.u8_bits[j] = tmp;                                     \
12.                         }
13. //+----------------+
14.     union un_01
15.     {
16.         ulong value;
17.         uchar u8_bits[sizeof(ulong)];
18.     };
19. 
20.     {
21.         un_01 info;
22. 
23.         info.value = 0xA1B2C3D4E5F6789A;
24.         PrintFormat("The region is composed of %d bytes", sizeof(info));
25.         PrintFormat("Before modification: 0x%I64X", info.value);
26.         macro_Swap(info);
27.         PrintFormat("After modification : 0x%I64X", info.value);
28.     }
29. 
30.     {
31.         un_01 info;
32. 
33.         info.value = 0xCADA;
34.         PrintFormat("The region is composed of %d bytes", sizeof(info));
35.         PrintFormat("Before modification: 0x%I64X", info.value);
36.         macro_Swap(info);
37.         PrintFormat("After modification : 0x%I64X", info.value);
38.     }
39. }
40. //+------------------------------------------------------------------+

Код 01

Когда код 01 компилируется и выполняется в MetaTrader 5, получается следующее.

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

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

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

Следующим естественным шагом будет замена кода 01 на немного другой. Это делается до того, как достигнем того, что показано в предыдущей статье. Это приводит к появлению такого кода:.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06. //+----------------+
07. #define macro_Swap(X)   for (uchar i = 0, j = sizeof(X) - 1, tmp; i < j; i++, j--)  \
08.                         {                                                           \
09.                             tmp = X.u8_bits[i];                                     \
10.                             X.u8_bits[i] = X.u8_bits[j];                            \
11.                             X.u8_bits[j] = tmp;                                     \
12.                         }
13. //+----------------+
14. 
15.     {
16.         union un_01
17.         {
18.             ulong value;
19.             uchar u8_bits[sizeof(ulong)];
20.         };
21.         
22.         un_01 info;
23. 
24.         info.value = 0xA1B2C3D4E5F6789A;
25.         PrintFormat("The region is composed of %d bytes", sizeof(info));
26.         PrintFormat("Before modification: 0x%I64X", info.value);
27.         macro_Swap(info);
28.         PrintFormat("After modification : 0x%I64X", info.value);
29.     }
30. 
31.     {
32.         union un_01
33.         {
34.             ushort value;
35.             uchar u8_bits[sizeof(ushort)];
36.         };
37. 
38.         un_01 info;
39. 
40.         info.value = 0xCADA;
41.         PrintFormat("The region is composed of %d bytes", sizeof(info));
42.         PrintFormat("Before modification: 0x%I64X", info.value);
43.         macro_Swap(info);
44.         PrintFormat("After modification : 0x%I64X", info.value);
45.     }
46. }
47. //+------------------------------------------------------------------+

Код 02

Хорошо, когда мы запускаем код 02, конечным результатом будет то, что мы видим на изображении ниже.

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

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

Как видите, мы делаем что-то очень простое, но код начал становиться немного запутанным. Именно в этот момент возникает идея использования шаблонов типов. Причина в том, что единственная разница между блоком кода, который находится между строками 15 и 29, и блоком кода между строками 31 и 45, заключается в типе, который определен внутри объединения. Это можно увидеть в строках 18 и 34 из кода 02.

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. template <typename T>
05. union un_01
06. {
07.     T value;
08.     uchar u8_bits[sizeof(T)];
09. };
10. //+------------------------------------------------------------------+
11. void OnStart(void)
12. {
13. //+----------------+
14. #define macro_Swap(X)   for (uchar i = 0, j = sizeof(X) - 1, tmp; i < j; i++, j--)  \
15.                         {                                                           \
16.                             tmp = X.u8_bits[i];                                     \
17.                             X.u8_bits[i] = X.u8_bits[j];                            \
18.                             X.u8_bits[j] = tmp;                                     \
19.                         }
20. //+----------------+
21. 
22.     {
23.         un_01 info;
24. 
25.         info.value = 0xA1B2C3D4E5F6789A;
26.         PrintFormat("The region is composed of %d bytes", sizeof(info));
27.         PrintFormat("Before modification: 0x%I64X", info.value);
28.         macro_Swap(info);
29.         PrintFormat("After modification : 0x%I64X", info.value);
30.     }
31.     
32.     {
33.         un_01 info;
34. 
35.         info.value = 0xCADA;
36.         PrintFormat("The region is composed of %d bytes", sizeof(info));
37.         PrintFormat("Before modification: 0x%I64X", info.value);
38.         macro_Swap(info);
39.         PrintFormat("After modification : 0x%I64X", info.value);
40.     }
41. }
42. //+------------------------------------------------------------------+

Код 03

И вот тут-то и начинается путаница. А всё связано со следующим: код 03 пытается создать то, что показано в коде 02, но с использованием чего-то очень похожего на показанное в коде 01. Однако при компиляции кода 03 компилятор выдает следующее сообщение об ошибке.

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

Здесь определенно что-то не так, но на первый взгляд это не имеет никакого смысла. Потому что, на первый взгляд, мы правильно объявляем шаблон. Так что же не так с кодом 03, что мешает ему компилироваться? Если он не компилируется, значит, компилятор не понимает, что нужно сделать.

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

Однако есть понятие, которое мы объясняли в первых статьях о переменных и константах. Данная концепция очень важна сейчас для того, чтобы мы понимали, как поступать в подобных ситуациях. Как видите, в строке 07 из кода 03 мы объявляем переменную, верно? Я просто спрашиваю: какую переменную мы объявляем в строке 07? В этом и заключается смысл. Если ни я, ни любой другой программист не знает, то как об этом узнает компилятор?

Хорошо, вы можете ответить мне: «В строке 25 указываем, что нам нужен тип шириной восемь байт, а в строке 35 - два байта». Верно. Но это не указывает компилятору, какой тип переменной использовать. Прошу заметить, что в строках 25 и 35 МЫ НЕ ОБЪЯВЛЯЕМ ПЕРЕМЕННУЮ. Мы присваиваем ей значение. Данное объявление содержится в строках 23 и 33. Теперь вы видите в чем проблема?

Но есть и другая, еще более серьезная проблема, которая как раз и приводит к появлению всех тех сообщений об ошибках, которые можно увидеть на изображении 03: дело в том, что, хотя объявление показалось в строках 23 и 33, оно объявляет нечто, неизвестное компилятору, а именно тип переменной в строке 07. Обратите внимание, что строки 23 и 33 ссылаются на тип, объявленный в строке 05. Другими словами, объединение.

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

Зная это, вы можете спросить: как указать компилятору, какой тип данных использовать? Обычно мы используем тип данных, за которым следует название переменной. И поскольку мы делаем это в строках 23 и 33 из кода 03, я не представляю, как решить эту проблему. Если вы дошли до этого момента и разобрались во всех этих понятиях, пора посмотреть, как решить проблему, для того, чтобы уметь использовать шаблоны и перегружать различные типы. Для этого в MQL5 используется специальное объявление переменных, которые могут быть локальными или глобальными, уже существующие в языках C и C++. Но помните: главное - понять концепцию, а не просто запомнить, как это делать. Решение находится чуть ниже.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. template <typename T>
05. union un_01
06. {
07.     T value;
08.     uchar u8_bits[sizeof(T)];
09. };
10. //+------------------------------------------------------------------+
11. void OnStart(void)
12. {
13. //+----------------+
14. #define macro_Swap(X)   for (uchar i = 0, j = sizeof(X) - 1, tmp; i < j; i++, j--)  \
15.                         {                                                           \
16.                             tmp = X.u8_bits[i];                                     \
17.                             X.u8_bits[i] = X.u8_bits[j];                            \
18.                             X.u8_bits[j] = tmp;                                     \
19.                         }
20. //+----------------+
21. 
22.     {
23.         un_01 <ulong> info;
24. 
25.         info.value = 0xA1B2C3D4E5F6789A;
26.         PrintFormat("The region is composed of %d bytes", sizeof(info));
27.         PrintFormat("Before modification: 0x%I64X", info.value);
28.         macro_Swap(info);
29.         PrintFormat("After modification : 0x%I64X", info.value);
30.     }
31.     
32.     {
33.         un_01 <ushort> info;
34. 
35.         info.value = 0xCADA;
36.         PrintFormat("The region is composed of %d bytes", sizeof(info));
37.         PrintFormat("Before modification: 0x%I64X", info.value);
38.         macro_Swap(info);
39.         PrintFormat("After modification : 0x%I64X", info.value);
40.     }
41. }
42. //+------------------------------------------------------------------+

Код 04

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

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

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

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

Какая прекрасная и удивительная вещь, не правда ли? Но что здесь произошло, почему код 04 работает, а код 03 - нет, и почему мы делаем это странное объявление в строках 23 и 33? «Теперь я в полном недоумении. Ошарашен. Потому что я абсолютно не понимаю, что здесь происходит».

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

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

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

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


Зачем усложнять, если можно упростить?

Многие из вас могут подумать, что наши действия здесь нелогичны, этот материал слишком продвинутый и что нам не нужно учиться тому, как это делать. На самом деле, я вынужден согласиться с данным утверждением. Если вы умеете только создавать функции и процедуры, объявлять переменные и использовать некоторые базовые операторы, вы можете создать практически всё, что угодно. Чем больше инструментов и ресурсов в нашем распоряжении, тем проще их внедрять и создавать. С помощью одного гвоздя можно даже построить элеватор для хранения зерна. Такое уже было сделано. Но было бы гораздо проще, если бы мы могли использовать больше ресурсов в качестве «гвоздей».

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

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. template <typename T>
05. union un_01
06. {
07.     T value;
08.     uchar u8_bits[sizeof(T)];
09. };
10. //+------------------------------------------------------------------+
11. void OnStart(void)
12. {
13.     {
14.         un_01 <ulong> info;
15. 
16.         info.value = 0xA1B2C3D4E5F6789A;
17.         PrintFormat("The region is composed of %d bytes", sizeof(info));
18.         PrintFormat("Before modification: 0x%I64X", info.value);
19.         Swap(info);
20.         PrintFormat("After modification : 0x%I64X", info.value);
21.     }
22.     
23.     {
24.         un_01 <ushort> info;
25. 
26.         info.value = 0xCADA;
27.         PrintFormat("The region is composed of %d bytes", sizeof(info));
28.         PrintFormat("Before modification: 0x%I64X", info.value);
29.         Swap(info);
30.         PrintFormat("After modification : 0x%I64X", info.value);
31.     }
32. }
33. //+------------------------------------------------------------------+
34. void Swap(un_01 &arg)
35. {
36.     for (uchar i = 0, j = sizeof(arg) - 1, tmp; i < j; i++, j--)
37.     {
38.         tmp = arg.u8_bits[i];
39.         arg.u8_bits[i] = arg.u8_bits[j];
40.         arg.u8_bits[j] = tmp;
41.     }
42. }
43. //+------------------------------------------------------------------+

Код 05

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

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

Опять это? Это уже не смешно. Спокойно, нет причин для паники или отчаяния. Пока нет. Проблема очень похожа на ту, что была озвучена в предыдущей теме. Но в данном случае решение другое. Прошу заметить следующее: в строках 19 и 29 происходит вызов процедуры в строке 34. Пока всё хорошо. Проблема в том, что процедура ожидает именно тот тип данных, который определен в строке 05. А поскольку данный тип является шаблоном, компилятор не знает, как с ним поступить, поскольку мы не можем указать ему, какой тип данных использовать.

«Но подождите. Что вы имеете в виду? Мы объявляем тип данных в строках 14 и 24». Да, но в этих строках 14 и 24 мы локально определяем тип данных, который будет использоваться. Однако это не передается в процедуру из строки 34, именно потому, что это сложный тип, а не первичный, как это было в прошлом, когда всё передавалось как есть. Есть и еще один небольшой момент. Поскольку тип данных допускает перегрузку типов, при попытке передать его внутри функции или процедуры мы должны убедиться в том, что функция или процедура ТАКЖЕ МОЖЕТ БЫТЬ ПЕРЕГРУЖЕНА. Вот почему я сказал, что такие моменты интересны.

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

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. template <typename T>
05. union un_01
06. {
07.     T value;
08.     uchar u8_bits[sizeof(T)];
09. };
10. //+------------------------------------------------------------------+
11. void OnStart(void)
12. {
13.     {
14.         un_01 <ulong> info;
15. 
16.         info.value = 0xA1B2C3D4E5F6789A;
17.         PrintFormat("The region is composed of %d bytes", sizeof(info));
18.         PrintFormat("Before modification: 0x%I64X", info.value);
19.         Swap(info);
20.         PrintFormat("After modification : 0x%I64X", info.value);
21.     }
22.     
23.     {
24.         un_01 <ushort> info;
25. 
26.         info.value = 0xCADA;
27.         PrintFormat("The region is composed of %d bytes", sizeof(info));
28.         PrintFormat("Before modification: 0x%I64X", info.value);
29.         Swap(info);
30.         PrintFormat("After modification : 0x%I64X", info.value);
31.     }
32. }
33. //+------------------------------------------------------------------+
34. template <typename T>
35. void Swap(un_01 &arg)
36. {
37.     for (uchar i = 0, j = sizeof(arg) - 1, tmp; i < j; i++, j--)
38.     {
39.         tmp = arg.u8_bits[i];
40.         arg.u8_bits[i] = arg.u8_bits[j];
41.         arg.u8_bits[j] = tmp;
42.     }
43. }
44. //+------------------------------------------------------------------+

Код 06

Супер! Теперь у нас есть правильный код. Компилятор наконец-то сможет понять, что мы пытаемся сделать. И вот, мы попросим у компилятора создать исполняемый файл, поскольку перегрузка с использованием шаблона была реализована, как можно видеть в коде 06. Однако, к большому сожалению, компилятор сообщает нам о том, что мы видим чуть ниже.

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

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

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

Будьте внимательны. Хотя компилятор сообщает, что ошибка находится в строках 19 и 29, он ведет нас не туда. Это не вина компилятора, а наша. Объясню почему. Помните ли вы, что в предыдущей теме необходимо было предпринять шаги, чтобы компилятор мог понять, какие данные мы используем? Мы создали объявление, которое можно увидеть в коде 06, в строках 14 и 24.

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

Прошу заметить, что при объявлении шаблона в строке 34, мы указываем, что собираемся использовать тип T для создания перегрузки, верно? Теперь снова посмотрите на строку 35. Какой аргумент, который в данном случае только один, получает данный тип T? Не существует аргумента, принимающего этот тип T. То есть, хотя объявление и сообщает, что мы создаем шаблон, на самом деле он не используется. Теперь давайте вернемся к предыдущим статьям и посмотрим, как было сделано заявление. Вы увидите нечто похожее на это:

                   .
                   .
                   .
15. template <typename T>
16. T Averange(const T &arg[])
                   .
                   .
                   .

Фрагмент 01

В фрагменте 01 обратите внимание на следующий факт: тип T объявляется в строке 15, но сразу после этого мы используем его в строке 16. Это нужно для того, чтобы аргумент мог использовать тип, когда компилятор будет создавать функцию или процедуру. Если этого не сделать, компилятор не будет знать, как действовать. То же самое происходит в коде 06: у нас есть декларация, но мы ее не используем. Другими словами, мы не указываем компилятору, как использовать объявление. Для этого мы должны снова изменить код 06, чтобы компилятор понял, что мы хотим сделать. Ниже приведен код, который будет работать.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. template <typename T>
05. union un_01
06. {
07.     T value;
08.     uchar u8_bits[sizeof(T)];
09. };
10. //+------------------------------------------------------------------+
11. void OnStart(void)
12. {
13.     {
14.         un_01 <ulong> info;
15. 
16.         info.value = 0xA1B2C3D4E5F6789A;
17.         PrintFormat("The region is composed of %d bytes", sizeof(info));
18.         PrintFormat("Before modification: 0x%I64X", info.value);
19.         Swap(info);
20.         PrintFormat("After modification : 0x%I64X", info.value);
21.     }
22.     
23.     {
24.         un_01 <ushort> info;
25. 
26.         info.value = 0xCADA;
27.         PrintFormat("The region is composed of %d bytes", sizeof(info));
28.         PrintFormat("Before modification: 0x%I64X", info.value);
29.         Swap(info);
30.         PrintFormat("After modification : 0x%I64X", info.value);
31.     }
32. }
33. //+------------------------------------------------------------------+
34. template <typename T>
35. void Swap(un_01 <T> &arg)
36. {
37.     for (uchar i = 0, j = sizeof(arg) - 1, tmp; i < j; i++, j--)
38.     {
39.         tmp = arg.u8_bits[i];
40.         arg.u8_bits[i] = arg.u8_bits[j];
41.         arg.u8_bits[j] = tmp;
42.     }
43. }
44. //+------------------------------------------------------------------+

Код 07

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

Но без использования шаблона и обязательно используя шаблон объединения, объявленный в строке 04 из кода 07. Смогли бы вы это сделать? Смогли бы вы решить эту задачу, чтобы получить работу программиста, или вы просто скажете, что это невозможно? В какой-то момент мы упомянули, что процедура или функция, в данном случае, должна использовать шаблон, чтобы быть сгенерированной.

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


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

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

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

Прикрепленные файлы |
Anexo.zip (3.9 KB)
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Управление рисками (Часть 2): Реализация расчета лотов в графическом интерфейсе Управление рисками (Часть 2): Реализация расчета лотов в графическом интерфейсе
В этой статье мы рассмотрим, как улучшить и более эффективно применять концепции, изложенные в предыдущей статье, используя мощные библиотеки графических элементов управления MQL5. Я шаг за шагом проведу вас через процесс создания полностью функционального графического интерфейса, объясняя стоящий за ним план проектирования, а также назначение и принцип работы каждого используемого метода. Кроме того, в конце статьи мы протестируем созданную нами панель, чтобы убедиться в ее корректной работе и соответствии заявленным целям.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Нейросети в трейдинге: Модель темпоральных запросов (Окончание) Нейросети в трейдинге: Модель темпоральных запросов (Окончание)
Представляем вашему вниманию завершающий этап реализации и тестирования фреймворка TQNet, в котором теория встречается с реальной торговой практикой. Мы пройдём путь от исторического обучения до стресс-теста на свежих рыночных данных, оценивая устойчивость и точность модели. Итоговые результаты — это не только сухие цифры, но и наглядная демонстрация прикладной ценности предложенного подхода.