从基础到中级:模板和类型名称(四)
概述
此处提供的材料仅用于教育目的。它不应以任何方式被视为最终应用程序。其目的不是探索所提出的概念。
在上一篇文章:“从基础到中级:模板和类型名称(三) ”中,我们开启了一个让很多新手感到特别有挑战性的话题。这是因为许多人还没有理解一个对 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 行声明的值是两个字节宽。然而,正是因为联合体有 8 个字节宽,我们被迫使用这种类型的声明,这使得最终结果完全不够,正如您在图 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 中我们得到了我们想要实现的结果。也就是说,现在我们有一个要显示的类型值,需要 8 个字节。另一个值也会显示出来,需要两个字节,正如预期的那样。然而,看看它是如何完成的。即使它正在运行,我们也会通过创建代码并对其进行适当的更改来重载自己。随着代码的增长和变得更加复杂,这增加了出错的机会,因为我们必须向其中添加越来越多的新元素。
正如你所看到的,我们正在做一些非常简单的事情,但代码已经开始有点令人困惑。正是在这一点上,出现了使用类型模板的想法。原因是第 15 行和第 29 行之间的代码块与第 31 行和第 45 行之间的代码块之间的唯一区别在于联合内部定义的类型。您可以在代码 02 的第 18 行和 34 行看到它。
因此,我们开始创建一个模板,其主要目的是简化相同的代码 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 所示)并没有向我们指出问题所在。
然而,我们在第一篇文章中解释了一个关于变量和常量的概念。这一概念现在非常重要,以便我们了解如何在这种情况下采取行动。正如你所看到的,在代码 03 的第 07 行我们声明了一个变量,对吗?我只是问:我们在第 07 行声明了什么变量?这就是重点。如果我和其他程序员都不知道,那么编译器怎么知道呢?
好吧,你可以回答我:“在第 25 行中,我们指定我们想要一个宽度为 8 个字节的类型,在第 35 行中,我们指定我们想要一个宽度为 2 个字节的类型。”这是正确的,但这并没有告诉编译器要使用哪种类型的变量。请注意,在第 25 行和第 35 行我们没有声明变量。我们给它分配一个值。该声明包含在第 23 行和第 33 行。你现在看到问题了吗?
但还有另一个更严重的问题,它导致了图 03 中显示的所有错误消息。事实是,尽管声明出现在第 23 行和第 33 行,但它声明了一些编译器不知道的内容,即第 07 行中的变量类型。请注意,第 23 行和第 33 行指的是第 05 行声明的类型。换句话说,是一个联合。
然而,这个联合只是一个模板。因此,要使用的数据类型由编译器决定。由于它不知道使用哪种数据类型,所有这些错误消息都会出现。但现在有一个概念应该被接受。如果你了解模板在函数和过程中的使用方式,你可能知道在某个时刻会声明一个变量。当函数或过程被重载时,编译器已经知道数据类型,因此它可以创建适当的过程。
了解了这一点,您可能会问:我如何向编译器指出要使用哪种数据类型?我们通常使用数据类型后跟变量名。由于我们在代码 03 的第 23 行和 33 行中执行了此操作,所以我不知道如何解决这个问题。如果你已经达到了这一点,并弄清楚了所有这些概念,那么是时候看看如何解决这个问题,以便能够使用模板和重载不同的类型了。为此,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 声明中附带的字母 T 实际上是用于稍后定义某些内容的标签。因此,当编译器替换 T 时,我们将得到我们需要使用的类型定义。并且编译器将能够正确创建代码。因此,当我们在第 23 行和第 33 行的声明中以这种方式声明类型时,我们实际上是告诉编译器使用哪种数据类型,而不是类型名声明中附带的 T。
由于我们还没有详细讨论如何使用此声明,因此您可能需要一段时间才能理解它。然而,正如你所看到的,它是有效的。因此,声明应该这样设计。
如果你觉得这很有趣,那么你会喜欢接下来的内容,因为现在我们要将宏从代码 04 转换为函数或过程。目标是让代码 04 继续工作并获得图 05 中显示的结果。但是,由于这意味着以更高级的形式应用当今的材料,我们将单独考虑这个问题。
当你可以简化它时,为什么要把它复杂化?
你们中的许多人可能会认为我们在这里的行为不合逻辑,这种材料太先进了,我们不必学习如何去做。事实上,我必须同意这一说法。如果你只知道如何创建函数和过程、声明变量和使用一些基本语句,你几乎可以创建任何东西。我们拥有的工具和资源越多,实施和创建它们就越容易。只需一颗钉子,你甚至可以建造一个粮仓升降机。这项工作已经完成。但如果我们能将更多的资源用作 “钉子”,事情就会容易得多。
现在我们将创建一个函数来替换代码 04 第 14 行定义的宏。这将会非常有趣。但首先,我必须提醒大家,我们的目标是学习。在试图理解我们在这里做什么之前,有必要了解上一个主题中做了什么。只有这样,一切才有意义。
下一步,让我们从最初的想法开始。也就是把代码 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 就变得像 ABC 一样简单。几乎就像小孩子的游戏。但很难理解我们在这里解释的内容。
当心,虽然编译器报告错误在第 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 生成相同结果的代码。
但不使用模板,而必须使用代码 07 第 04 行声明的联合模板。你能做到吗?为了找到一份程序员的工作,你能解决这个问题吗?或者你会说这是不可能的?在某些时候,我们提到,在这种情况下,过程或函数必须使用要生成的模板。
有些概念和细节让事情变得非常有趣。例如,我们可以用另一种方式创建代码 07 来直接管理重载,而无需为此使用模板。由于理解这些事情需要适当的解释,我们将在下一篇文章中考虑这个问题。
最后的探讨
在本文中,我们讨论了如何管理模板并以更通用和精确的方式创建模板。因为我知道这些内容很难立刻理解,因为我在编程生涯初期学习 C 和 C++ 时自己也经历过这些,所以我请您耐心等待。我建议你练习这些文章中展示的内容,最重要的是,试着理解所提出的概念。如果你理解它们,你几乎可以创建任何代码,而且比那些坚持记忆代码片段和编程语言语法的人要容易得多。因此,要冷静地实践和学习应用的内容。我们将在下一篇文章中与您见面。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/15670
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
在 MQL5 中创建交易管理面板(第九部分):代码组织(三):通信模块
MQL5 交易工具包(第 7 部分):使用最近取消的挂单函数扩展历史管理 EX5 库
MQL5自动化交易策略(第十四部分):基于MACD-RSI统计方法的交易分层策略
从基础到中级:模板和类型名称(三)