
От начального до среднего уровня: Шаблон и Typename (V)
Введение
В предыдущей статье От начального до среднего уровня: Шаблон и Typename (IV), мы объяснили, как можно создать шаблон для обобщения типа моделирования самым дидактичным и простым способом. Таким образом, мы создаем то, что условно говоря, можно считать перегрузкой типа данных. В конце статьи мы представили концепцию, которая многим может показаться сложной для понимания: передача данных внутри функции или процедуры, которая также реализована в виде шаблона. Именно потому что представленная информация требует более детального объяснения, мы оставили это для нынешней статьи. Кроме того, мы должны обсудить еще один связанный с этим вопрос, где использование шаблонов может подчеркнуть разницу между реализацией чего-либо или отсутствием реализации.
Для того, чтобы должным образом начать данную статью, давайте заведем новую тему, в которой объясним, почему работает показанный в предыдущей статье код.
Расширяем сознание
В предыдущей статье мы реализовали нечто довольно необычное на первый взгляд. Не думаю, что многие из вас видели что-то подобное раньше. Чтобы правильно объяснить его значение, необходимо рассмотреть используемый код. Вы найдете его чуть ниже.
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. //+------------------------------------------------------------------+
Код 01
Ладно, поскольку вы уже успели познакомиться с тем, что было в предыдущей статье, код 01 наверняка привлек ваше внимание. Это связано с тем, как объявлена процедура в строке 35. Действительно, чтобы понять то, что нужно объявить, важнее всего понять, почему это построено именно так, как сделано в коде 01.
Прежде всего, мы должны понимать, что процедура в строке 35 будет перегружена компилятором. Это происходит в процессе создания исполняемого кода. Как уже говорилось в предыдущей статье, мы поставил перед собой задачу создать код, который выполнял бы ту же работу, что и код 01, но без использования перегрузки шаблона. Это для процедуры, реализованной в строке 35. Цель данной задачи - прояснить, почему процедура должна быть изложена именно таким образом.
Поскольку эта задача проста в теории, но может быть немного сложной на практике, давайте сделаем это вместе. Тогда вы сможете понять, почему объявление должно быть сделано именно таким образом, как в коде 01.
Мы не собираемся изменять весь код, только процедуру. Таким образом, мы можем сосредоточиться на приведенном ниже фрагменте.
. . . 33. //+------------------------------------------------------------------+ 34. void Swap(un_01 <ulong> &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. //+------------------------------------------------------------------+ 44. void Swap(un_01 <ushort> &arg) 45. { 46. for (uchar i = 0, j = sizeof(arg) - 1, tmp; i < j; i++, j--) 47. { 48. tmp = arg.u8_bits[i]; 49. arg.u8_bits[i] = arg.u8_bits[j]; 50. arg.u8_bits[j] = tmp; 51. } 52. } 53. //+------------------------------------------------------------------+
Фрагмент 01
Данный фрагмент заменяет процедуру в строке 35. Но обратите внимание: то, что содержится во фрагменте 01, - это ТОЧНО то, что создаст компилятор когда осуществит перевод и перегрузку процедуры, показанной в коде 01. Поэтому во фрагменте 01 рассматриваются только типы ulong и ushort, в отличие от кода 01, где рассматриваются все первичные типы.
Но тогда почему мы должны выражать всё именно таким образом, как показано во фрагменте 01? Причина, как мне кажется, теперь гораздо яснее. Если вы поняли материал из предыдущей статьи, вы поймете, почему строки 14 и 24 из кода 01 объявлены именно таким образом. И по той же причине нам нужно, чтобы строки 34 и 44, показанные во фрагменте 01, были объявлены аналогичным образом.
Помните следующий факт, который был показан в первых статьях, когда говорилось о передаче значения или по ссылке: при объявлении переменной мы должны указать ее тип и дать ей название. А в объявлении функции или процедуры мы объявляем переменную, которая может быть константой или может и не быть константой, в зависимости от каждого конкретного случая.
«Ладно, что касается объявления переменных для работы с процедурой, то здесь всё просто. Но я всё еще сомневаюсь. Когда вы говорили о специальных переменных, вы сказали, что функция - это один из таких типов переменных. Однако в такой ситуации, как та, с которой мы имеем дело сейчас, как мы можем использовать функцию, чтобы охватывать код и реализовать то, что нам нужно?»
Что ж, это действительно отличный вопрос. Это связано с тем, что для возврата значений нам нужно использовать другое объявление, чем при использовании процедур. Однако принятая концепция весьма схожа с тем, что мы видели до этого момента. Помните, что, когда мы возвращаем что-то, возвращаемый элемент является типом переменной, как если бы мы объявили переменную, название которой - название функции. Поэтому мы можем применить базовую концепцию и создать наш код соответствующим образом.
Прежде, чем рассматривать обобщенную форму, мы увидим форму, аналогичную форме из фрагмента 01. Однако, поскольку функции должны работать иначе, чем процедуры, мы также изменим код 01. Вот что мы видим ниже.
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. PrintFormat("After modification : 0x%I64X", Swap(info).value); 20. } 21. 22. { 23. un_01 <ushort> info; 24. 25. info.value = 0xCADA; 26. PrintFormat("The region is composed of %d bytes", sizeof(info)); 27. PrintFormat("Before modification: 0x%I64X", info.value); 28. PrintFormat("After modification : 0x%I64X", Swap(info).value); 29. } 30. } 31. //+------------------------------------------------------------------+ 32. un_01 <ulong> Swap(const un_01 <ulong> &arg) 33. { 34. un_01 <ulong> local; 35. 36. for (uchar i = 0, j = sizeof(arg) - 1, tmp; i < j; i++, j--) 37. { 38. tmp = arg.u8_bits[i]; 39. local.u8_bits[i] = arg.u8_bits[j]; 40. local.u8_bits[j] = tmp; 41. } 42. 43. return local; 44. } 45. //+------------------------------------------------------------------+ 46. un_01 <ushort> Swap(const un_01 <ushort> &arg) 47. { 48. un_01 <ushort> local; 49. 50. for (uchar i = 0, j = sizeof(arg) - 1, tmp; i < j; i++, j--) 51. { 52. tmp = arg.u8_bits[i]; 53. local.u8_bits[i] = arg.u8_bits[j]; 54. local.u8_bits[j] = tmp; 55. } 56. 57. return local; 58. } 59. //+------------------------------------------------------------------+
Код 02
Прошу заметить, что код 02 немного отличается от кода 01. Однако, несмотря на эти небольшие различия, результат получается один и тот же. То есть, скомпилировав код 02 и запустив его в терминале MetaTrader 5, мы получим то, что показано на рисунке ниже.
Изображение 01
Обратите внимание, что это тот же тип ответа, который мы получили бы при выполнении кода 01, даже после его модификации с использованием фрагмента 01. Но, как и фрагмент 01, код 02 ограничивает выбор типов данных, которые компилятор сможет использовать, когда мы попытаемся применить объединение un_01.
Здесь, в коде 02, у нас то же самое ограничение. Однако я хочу, чтобы вы посмотрели, как был переведен фрагмент 01, чтобы использовать функции вместо процедур. Посмотрите также на строки 19 и 28 из кода 02, поскольку именно здесь мы используем особую переменную Swap, которая на самом деле является функцией с благородной целью быть использованной в качестве переменной только для чтения.
Замечательно, правда? Но так же, как фрагмент 01 показывает, что делает процедура в коде 01, мы можем преобразовать эти функции из кода 02 в шаблон. Таким образом, код сохранит то же поведение и свободу выбора типов, что и код 01. Для этого достаточно преобразовать общие точки функций из кода 02 в точку, в которой компилятор может действовать определенным образом. В результате компилятор сможет изменять тип данных, когда это будет необходимо. Понимая данную концепцию, мы можем превратить код 02 в код 03, который следует непосредственно ниже.
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. PrintFormat("After modification : 0x%I64X", Swap(info).value); 20. } 21. 22. { 23. un_01 <ushort> info; 24. 25. info.value = 0xCADA; 26. PrintFormat("The region is composed of %d bytes", sizeof(info)); 27. PrintFormat("Before modification: 0x%I64X", info.value); 28. PrintFormat("After modification : 0x%I64X", Swap(info).value); 29. } 30. } 31. //+------------------------------------------------------------------+ 32. template <typename T> 33. un_01 <T> Swap(const un_01 <T> &arg) 34. { 35. un_01 <T> local; 36. 37. for (uchar i = 0, j = sizeof(arg) - 1, tmp; i < j; i++, j--) 38. { 39. tmp = arg.u8_bits[i]; 40. local.u8_bits[i] = arg.u8_bits[j]; 41. local.u8_bits[j] = tmp; 42. } 43. 44. return local; 45. } 46. //+------------------------------------------------------------------+
Код 03
Код 03 является улучшением кода 02, потому что теперь мы можем использовать любой тип данных, и компилятор сможет понять его и создать все необходимые перегрузки для успешного создания нашего приложения.
В связи с этим то, что раньше казалось очень сложным и трудным для понимания, стало простым и тривиальным, и любой новичок теперь может разобраться с использованием шаблонов. Заметили ли вы, как иногда что-то нам кажется сложным, но если мы действительно понимаем принятые концепции, то с легкостью реализуем что угодно? Именно поэтому я говорю вам, что нужно практиковать то, что показывается здесь. Не для того, чтобы запомнить способы написания кода, а для того, чтобы понять, как концепции изучаются и применяются в каждой конкретной ситуации.
На самом деле, это та часть, которую многие считают промежуточной или даже продвинутой. Но, на мой взгляд, то, что здесь показано, - это только основы. Однако нам всё же нужно рассказать немного больше о том, что мы здесь делаем, поскольку пока не рассказали, почему мы всегда используем зарезервированное слово typename.
Но это объяснение требует от нас перехода к новой теме, чтобы всё было правильно разделено и мы могли спокойно и внимательно изучить эту тему. Так что давайте создадим новую тему.
Typename - для чего он нужен?
Один из вопросов, который на самом деле заслуживает соответствующего объяснения: что такое typename и для чего он нужен в реальном коде? Что ж, понимание этого может помочь вам реализовать некоторые довольно интересные типы кода. Однако в большинстве случаев typename используется только для очень специфического и целенаправленного тестирования.
Насколько мне известно, typename нечасто используется для чего-то другого, кроме как для проверки и обеспечения того, чтобы перегруженная компилятором функция или процедура не вышла из-под контроля. И это связано с тем, что нередко бывает так: мы реализуем шаблон, а при использовании функции или процедуры получаем противоречивые результаты именно потому, что тот или иной тип реализован неправильно.
В иных случаях мы можем пожелать, чтобы при использовании одного типа данных функция или процедура, перегруженная компилятором, вела себя конкретным образом, а при использовании другого типа данных - по-другому. Это относится к одной и той же функции или процедуре. Разговор об этом может показаться непонятным, но на практике это иногда оказывается необходимо. Поэтому понимание того, как работает typename, поможет справиться с подобными ситуациями.
Чтобы продемонстрировать это, мы попробуем создать приложение, которое будет, по крайней мере, интересным. Демонстрация работы с typename - это очень специфичный процесс, который может показаться скучным. Но давайте посмотрим, сможем ли мы сделать поинтереснее. Для этого мы используем приведенный ниже код.
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. Print("Demonstrating a mirror"); 14. 15. Check((ulong)0xA1B2C3D4E5F6789A); 16. Check((uint)0xFACADAFE); 17. Check((ushort)0xCADE); 18. Check((uchar)0xFE); 19. } 20. //+------------------------------------------------------------------+ 21. template <typename T> 22. void Check(const T arg) 23. { 24. un_01 <T> local; 25. string sz; 26. 27. local.value = arg; 28. PrintFormat("The region is composed of %d bytes", sizeof(local)); 29. PrintFormat("Before modification: 0x%I64X", local.value); 30. PrintFormat("After modification : 0x%I64X", Mirror(local).value); 31. StringInit(sz, 20, '*'); 32. Print(sz); 33. } 34. //+------------------------------------------------------------------+ 35. template <typename T> 36. un_01 <T> Mirror(const un_01 <T> &arg) 37. { 38. un_01 <T> local; 39. 40. for (uchar i = 0, j = sizeof(arg) - 1, tmp; i < j; i++, j--) 41. { 42. tmp = arg.u8_bits[i]; 43. local.u8_bits[i] = arg.u8_bits[j]; 44. local.u8_bits[j] = tmp; 45. } 46. 47. return local; 48. } 49. //+------------------------------------------------------------------+
Код 04
В коде 04 мы будем играть со всем, что мы видели и объясняли до этого момента. Наша цель - понять, как typename можно использовать в реальном коде. Задача - отражать всю информацию, находящуюся в памяти, то есть в переменной, так, чтобы правая половина перешла в левую, а левая поменялась местами с правой. Всё просто.
Взглянув на код 04, мы видим, что он является практически модификацией того, что мы видели до этих пор. И это делается вполне сознательно, поскольку так мы можем сосредоточиться на том, что действительно является новым и требует понимания.
Когда мы запускаем код 04 в MetaTrader 5, мы получаем следующий ответ.
Изображение 02
Обратите внимание, что мы выделяем одно из значений. Причина проста: ОНО НЕ ОТРАЖАЕТСЯ, поскольку правая сторона НЕ меняется местами с левой. Однако при всех остальных значениях зеркальное отображение происходит. Проблема в том, что, используя однобайтовый тип данных, теряется возможность зеркально отображать правую часть на левую и наоборот.
Но мы знаем, что в MQL5 есть только один тип, который удовлетворяет однобайтовому критерию. Это тип uchar, когда у нас нет значения со знаком, и тип char, когда у нас есть значение со знаком. Однако реализация перегруженного вызова для работы с этими типами несколько неудобна. Причина в том, что при таком подходе мы теряем «изономию» операции. В идеале для этого следует продолжать использовать функцию Mirror, которая реализована в строке 36.
Но теперь возникает ключевой вопрос: как мы можем указать компилятору, как работать с типом uchar или char, чтобы добиться зеркального отображения значений?
Что ж, есть много способов сделать это. Один из самых простых вариантов - читать бит за битом, чередуя левые и правые биты. Таким образом, нам не придется использовать перегрузку функций или процедур, как это обычно делается. Однако это не является нашей целью, поэтому предоставляем вам возможность реализовать подобное решение, в котором мы делаем зеркальное отражение, меняя местами биты, присутствующие во входных данных. Это поможет вам лучше усвоить некоторые понятия и даже мыслить как программист.
Но что же делать нам сейчас, как решить данную проблему? Ну что ж, вот тут-то всё и становится интересным. Это возможно, потому что typename может буквально сообщить нам название входящего типа данных. Другими словами, мы можем спросить typename о типе данных, которые мы получаем или которые используются переменной. Чтобы сделать это более понятным, мы создадим небольшую модификацию в коде 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. Print("Demonstrating a mirror"); 14. 15. Check((ulong)0xA1B2C3D4E5F6789A); 16. Check((uint)0xFACADAFE); 17. Check((ushort)0xCADE); 18. Check((uchar)0xFE); 19. } 20. //+------------------------------------------------------------------+ 21. template <typename T> 22. void Check(const T arg) 23. { 24. un_01 <T> local; 25. string sz; 26. 27. local.value = arg; 28. PrintFormat("The region is composed of %d bytes", sizeof(local)); 29. PrintFormat("Before modification: 0x%I64X", local.value); 30. PrintFormat("After modification : 0x%I64X", Mirror(local).value); 31. StringInit(sz, 20, '*'); 32. Print(sz); 33. } 34. //+------------------------------------------------------------------+ 35. template <typename T> 36. un_01 <T> Mirror(const un_01 <T> &arg) 37. { 38. un_01 <T> local; 39. 40. PrintFormat("Type is: [%s] or variable route is: {%s}", typename(T), typename(arg)); 41. for (uchar i = 0, j = sizeof(arg) - 1, tmp; i < j; i++, j--) 42. { 43. tmp = arg.u8_bits[i]; 44. local.u8_bits[i] = arg.u8_bits[j]; 45. local.u8_bits[j] = tmp; 46. } 47. 48. return local; 49. } 50. //+------------------------------------------------------------------+
Код 05
Когда запустим код 05, увидим нечто, очень похожее на изображение ниже.
Изображение 03
Здесь, на изображении 03, появилось нечто, чего я не понял. В его появлении НЕТ НИКАКОГО смысла. Поэтому выделили этот момент синим цветом. Это СТРАННО. Но дело не в этом. Нас действительно интересуют те части изображения 03, которые выделены красным и зеленым цветом. Откуда взялась такая информация? Что ж, эта информация была выведена из-за строки 40 в коде 05. Прошу внимания, потому что это очень важно, и вы должны хорошо это понять. В противном случае в будущем у вас могут возникнуть проблемы при использовании информации, выделенной красным и зеленым цветом на изображении 03.
ВСЕ КРАСНЫЕ ОТМЕТКИ относятся к тому, что мы делаем запрос компилятору сообщить нам, КАКОЙ ПРИМИТИВНЫЙ ТИП данных использует функция. ОТМЕТКИ ЗЕЛЕННЫМ ЦВЕТОМ означают, что компилятор ответил на наш запрос и сообщил нам о ТИПЕ ДАННЫХ, ИСПОЛЬЗУЕМЫХ В ПЕРЕМЕННОЙ. Прошу заметить, что в нашем вопросе есть очень тонкое различие. Однако ответ может быть совершенно иным.
Обычно, и это наиболее вероятно, многие программисты просят у компилятора сообщить тип данных переменной. Однако это не всегда дает нам правильный ответ, точнее, тот ответ, который мы ожидаем получить. И это связано с тем, что мы можем оказаться в ситуации, очень похожей на показанную выше, когда примитивный тип данных один, а тип данных, используемый переменной, - более сложный тип, но каким-то образом связанный с примитивным.
Но не стоит отчаиваться по поводу того, как это проверить. В приложении вы найдете эти коды, чтобы вы могли спокойно изучать их. Возвращаясь к нашему вопросу, нас интересует информация, выделенная красным цветом на изображении 03. Обратите внимание, что все они записаны так же, как мы объявили их на этапе реализации кода. Однако я хотел бы обратить ваше внимание на то, что эти значения - строки. То есть мы можем сравнивать их с другими строками на этапе выполнения кода. И это именно та часть, которая нас интересует.
Отлично, теперь у нас есть отправная точка. Необходимо провести небольшой тест, чтобы выделить типы uchar или char, которые являются однобайтовыми типами, для правильного отражения значений. Для этого нам понадобится внести небольшое изменение. Но на этот раз мы возвращаемся к исходному коду, т.е. к коду 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. Print("Demonstrating a mirror"); 14. 15. Check((ulong)0xA1B2C3D4E5F6789A); 16. Check((uint)0xFACADAFE); 17. Check((ushort)0xCADE); 18. Check((uchar)0xFE); 19. } 20. //+------------------------------------------------------------------+ 21. template <typename T> 22. void Check(const T arg) 23. { 24. un_01 <T> local; 25. string sz; 26. 27. local.value = arg; 28. PrintFormat("The region is composed of %d bytes", sizeof(local)); 29. PrintFormat("Before modification: 0x%I64X", local.value); 30. PrintFormat("After modification : 0x%I64X", Mirror(local).value); 31. StringInit(sz, 20, '*'); 32. Print(sz); 33. } 34. //+------------------------------------------------------------------+ 35. template <typename T> 36. un_01 <T> Mirror(const un_01 <T> &arg) 37. { 38. un_01 <T> local; 39. 40. if (StringFind(typename(T), "char") > 0) local.value = (arg.value << 4) | (arg.value >> 4); 41. else for (uchar i = 0, j = sizeof(arg) - 1, tmp; i < j; i++, j--) 42. { 43. tmp = arg.u8_bits[i]; 44. local.u8_bits[i] = arg.u8_bits[j]; 45. local.u8_bits[j] = tmp; 46. } 47. 48. return local; 49. } 50. //+------------------------------------------------------------------+
Код 06
А теперь обратите внимание. То, что мы делаем здесь, можно сделать несколькими способами. У каждого из них есть свои преимущества и недостатки, и с их помощью можно более или менее легко понять, что делается и как всё будет происходить. Если вы не очень хорошо понимаете код 06, не волнуйтесь. Вы будете иметь доступ к нему в приложении и сможете изменять его, чтобы понять, что происходит.
Однако перед этим, неплохо бы понять, что мы реализовали и какой конечный результат будет при выполнении этого кода. Иначе вы можете что-то изменить и получить результат, который отличается от ожидаемого, и подумать, что всё работает правильно.
Так что давайте приступим к делу. Сначала мы посмотрим, каков результат выполнения в терминале MetaTrader 5, его можно увидеть на изображении ниже.
Изображение 04
Просто замечательно. Код успешно справился со своей задачей - отразить значения каждой переменной таким образом, чтобы правая половина поменялась местами с левой, как и предполагалось. Но обратите внимание: для того, чтобы сделать это на основе кода 04, в код 06 добавили только строку 40. Конечно, нам нужно сделать небольшую поправку в строке 41, но это не та часть, на которую мы хотим обратить ваше внимание, а функция, которую используем для проверки того, будем ли мы использовать тот или иной метод для зеркального отображения.
Функция StringFind стандартной библиотеки MQL5 позволит нам найти определенный фрагмент в строке, возвращаемой по названию typename. Для нас это важно, потому что, НЕЗАВИСИМО от того, используем ли мы тип переменной или примитивный тип, который использовал компилятор, мы сможем получить вхождение искомого фрагмента. Но причина, по которой мы используем эту функцию, заключается в том, что StringFind даст нам результат и при возврате uchar, и при возврате char. Это связано с тем, что разница между ними заключается именно в букве u, которая присутствует в uchar, но отсутствует в char. В любом случае, мы проведем тест и сообщим о результатах.
Однако каждый случай - это отдельный случай. В зависимости от того, что мы хотим построить в нашем коде, эта небольшая разница между названиями типов с одинаковой шириной байта может повлиять на конечный результат. Это связано с тем, что в одном случае мы можем иметь отрицательные значения, а в другом - нет. Хотя явное разрешение типов может быть применено для исправления такого рода неудобств, следует быть особо осторожным при реализации кода, в котором мы хотим использовать информацию, которую может создать компилятор.
Теперь начинается самое интересное. Вы, наверное, заметили, что результат изображения 04 правильный. Но даже в этом случае необходимо учитывать результат работы изображения 03. Я не знаю точно, почему компилятор решил сыграть с нами злую шутку. А это происходит так, если мы изменяем код 06 таким образом, чтобы вывести на экран тот же тип информации, что и распечатал бы код 05, то результат будет правильным. Однако, когда мы перешли к компиляции кода 05, который генерировал необычный и фатальный результат, как можно видеть на изображении 03, произошло нечто СТРАННОЕ.
Для проверки, мы привели ниже модифицированный код, на который и ссылаемся.
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. Print("Demonstrating a mirror"); 14. 15. Check((ulong)0xA1B2C3D4E5F6789A); 16. Check((uint)0xFACADAFE); 17. Check((ushort)0xCADE); 18. Check((uchar)0xFE); 19. } 20. //+------------------------------------------------------------------+ 21. template <typename T> 22. void Check(const T arg) 23. { 24. un_01 <T> local; 25. string sz; 26. 27. local.value = arg; 28. PrintFormat("The region is composed of %d bytes", sizeof(local)); 29. PrintFormat("Before modification: 0x%I64X", local.value); 30. PrintFormat("After modification : 0x%I64X", Mirror(local).value); 31. StringInit(sz, 20, '*'); 32. Print(sz); 33. } 34. //+------------------------------------------------------------------+ 35. template <typename T> 36. un_01 <T> Mirror(const un_01 <T> &arg) 37. { 38. un_01 <T> local; 39. 40. PrintFormat("Type is: [%s] or variable route is: {%s}", typename(T), typename(arg)); 41. if (StringFind(typename(T), "char") > 0) local.value = (arg.value << 4) | (arg.value >> 4); 42. else for (uchar i = 0, j = sizeof(arg) - 1, tmp; i < j; i++, j--) 43. { 44. tmp = arg.u8_bits[i]; 45. local.u8_bits[i] = arg.u8_bits[j]; 46. local.u8_bits[j] = tmp; 47. } 48. 49. return local; 50. } 51. //+------------------------------------------------------------------+
Код 07
Прошу заметить, что код 07 представляет собой комбинацию кодов 05 и 06. Запустив его, вы увидите следующее:
Изображение 05
Шутка это или нет? Давайте посмотрим, кто понимает, что могло произойти при компиляции кода 05 или во время его выполнения. Слава богу, мы здесь не занимаемся чем-то не требующим точности и где небольшие промахи вполне терпимы. Но даже в таком случае это всё равно смешно.
Заключительные идеи
В этой статье мы завершили объяснение и обучение, необходимое для того, чтобы вы узнали, что такое перегрузка функций или процедур. Наша цель состоит в том, чтобы одна и та же функция или процедура могла использовать одно и то же название, но с данными разных типов.
Мы начали с простых примеров, как тот, который был показан в статье От начального до среднего уровня: Перегрузка, и мы перешли ко всё более сложным случаям, создавая шаблоны функций и процедур. Но, поскольку подобная концепция выходит за рамки функций и процедур, мы приняли механизм создания шаблонов, чтобы сэкономить время при программировании. Используя шаблоны в наших реализациях, мы фактически делегируем компилятору создание перегрузки функций и процедур, чтобы упростить нашу работу как программистов.
Однако мы можем распространить концепцию шаблонов на другие ситуации, например, на создание и манипулирование сложными типами данных. В данном случае, поскольку мы говорили только о объединениях, мы ограничиваем все концепции и приложения этим типом моделирования. Таким образом, стало возможным экспериментировать с перегрузкой типов. Это позволило нам реализовывать еще больше вещей с гораздо меньшим количеством кода, поскольку ответственность за настройку и поддержание всего в рамках стандартов перешла к компилятору. На нас только легла ответственность за то, чтобы указать компилятору, какой примитивный тип данных следует использовать.
Однако можно расширить возможности еще больше, позволив нам манипулировать вещами контролируемым образом, когда в нашем коде используется тот или иной примитивный тип данных. Для этого мы используем typename, который позволяет нам узнать название типа, используемого либо компилятором, либо переменной, с которой мы хотим работать.
Я знаю, что многим из вас, особенно новичкам, всё это может показаться очень сложным и запутанным. Но стоит помнить, что мы всё еще находимся на том уровне, который я считаю самым базовым и простым из возможных. Поэтому я советую вам изучить и применить на практике всё, о чем было рассказано. Сосредоточьтесь на изучении всех описанных в этих статьях материалах, потому что дальше с каждым днем всё будет только сложнее и интереснее. А для тех, кто действительно любит программирование, мы скоро попадем в новый парк развлечений под названием MQL5.
До встречи в следующей статье, где мы затронем еще более интересную и увлекательную тему.
Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/15671
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.




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