从基础到中级:结构(七)
概述
在上一篇文章“从基础到中级:结构(六)”中,我们探讨了如何开始构建一个简单数据结构的通用实现。尽管这种建模方式看似不寻常,但其实它的使用频率远超大多数人的想象。这是因为大型数据库在组织和检索数据时,也采用了非常相近的原理。
我明白,在我们的文章中涉及的这个话题,对许多人来说可能显得有些极端。然而,值得铭记的是,我们的目标是采用一种解释方式,使得将来无需澄清某些在我看来微不足道的细节。但是,既然你们中的大多数人都是通过这些文章来学习其他程序员的经历和知识的,那么亲爱的读者们,我在开始时所能传达给你们的一切,都将对你们未来大有裨益。
仅仅看到能运行的代码是不够的;我们必须理解它为何能运行,并且,如有必要,知道如何调整其他程序员编写的实现以满足我们的需求。要做到这一点,必须了解这些概念并理解如何应用它们。
与上一篇文章一样,我们开始实现一些非常有趣的内容,以便更好地理解如何在其他结构中使用结构,也许你对上一段代码的结果感到好奇。之所以能做到这一点,是因为我们利用了一个原本为某一目的而设计的结构,并最终将其成功应用于另一目的。在这种情况下,数据最初是双精度浮点数类型,但最终我们能够使用任何类型的数据。
然而,我们所展示的并不是我们想要达到的目标。为此,我们需要更进一步。但所有这些努力将来都会得到回报,我保证。那么,让我们从上一篇文章中断的地方继续。
结构的结构,第二部分:回归
在上一篇文章中,我们以如下所示的代码作为结尾。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. template <typename T> 05. struct st_Data 06. { 07. //+----------------+ 08. private: 09. //+----------------+ 10. struct st_Reg 11. { 12. T h_value; 13. uint k_value; 14. }Values[]; 15. //+----------------+ 16. string ConvertToString(T arg) 17. { 18. if ((typename(T) == "double") || (typename(T) == "float")) return DoubleToString(arg, 2); 19. if (typename(T) == "string") return arg; 20. 21. return IntegerToString(arg); 22. } 23. //+----------------+ 24. public: 25. //+----------------+ 26. bool Set(const uint &arg1[], const T &arg2[]) 27. { 28. if (arg1.Size() != arg2.Size()) 29. return false; 30. 31. ArrayResize(Values, arg1.Size()); 32. for (uint c = 0; c < arg1.Size(); c++) 33. { 34. Values[c].k_value = arg1[c]; 35. Values[c].h_value = arg2[c]; 36. } 37. 38. return true; 39. } 40. //+----------------+ 41. string Get(const uint index) 42. { 43. for (uint c = 0; c < Values.Size(); c++) 44. if (Values[c].k_value == index) 45. return ConvertToString(Values[c].h_value); 46. 47. return "-nan"; 48. } 49. //+----------------+ 50. }; 51. //+------------------------------------------------------------------+ 52. #define PrintX(X) Print(#X, " => ", X) 53. //+------------------------------------------------------------------+ 54. void OnStart(void) 55. { 56. const string T = "possible loss of data due to type conversion"; 57. const uint K[] = {2, 1, 4, 0, 7, 5, 3, 6}; 58. 59. st_Data <string> info; 60. string H[]; 61. 62. StringSplit(T, ' ', H); 63. info.Set(K, H); 64. PrintX(info.Get(3)); 65. } 66. //+------------------------------------------------------------------+
代码 01
虽然代码 01 有效,但有点烦人。这是因为当我们编译它时,会得到一个类似于下面所示的响应。

图 01
诸如此类的事情(如图 01 所示)会造成一些不便。这是因为,根据实现类型的不同,编译器消息可能会导致代码在未来出现错误。这并非因为代码有误,而是编译器可能正在提醒我们注意一个潜在的关键错误。如果我们忽视错误信息,最终可能会遗漏真正重要的信息。
总之,执行代码 01 时,我们将得到以下响应。

图 02
很有趣,不是吗?但在继续之前,让我们先解决图 01 中所示的问题。有多种方法可以做到这一点。然而,它们最终都会导致相同的结果:显性转换。因此,我们实现显式转换的方式并不重要,重要的是这件事发生了。为此,只需将代码 01 更改为类似这样的内容:
. . . 15. //+----------------+ 16. string ConvertToString(T arg) 17. { 18. if (typename(T) == "double") return DoubleToString((double)arg, 2); 19. if (typename(T) == "float") return DoubleToString((float)arg, 2); 20. if (typename(T) == "string") return (string)arg; 21. return IntegerToString((long)arg); 22. } 23. //+----------------+ 24. public: 25. //+----------------+ . . .
代码 02
当你看到图 01 中问题的解决方案是代码 02 中的代码片段时,你可能会有些失望。许多人可能期望创建一整套指令和函数,但最终,我们只需要执行代码 02 中所示的操作即可。这是流程的一部分。我们感到失望的程度通常取决于我们最初的期望值有多高。玩笑归玩笑,让我们回到代码,了解如何使其更具可扩展性,以便在不进行重大更改的情况下处理不同类型的数据。
你可能会觉得这会很复杂。然而,在解释如何操作之前,我需要你真正理解如何以及为何将一个数组的元素链接到另一个数组的元素。
这个概念本身非常简单,它只是关于建立一种映射关系。换言之,如果我们知道将作为键的数组元素,那么返回结果本质上就是另一个数组中的某个元素。这正是我们将对代码 01 所做的事情。为何不为此使用一个多维数组呢?原因在于,尽管在这种情况下多维数组是最佳选择,但由于我们只是将其作为概念验证,因此并不适合使用。换句话说,这是为了展示如何使用某种特定类型的语言资源。
好的,我认为我们已经足够清楚地解释了我们正在做的事情,我希望您再看一下代码 01,并告诉我是什么阻碍了它不仅在翻译值时使用,而且在其他类似情境中的使用。
不要急于下结论,先仔细考虑一下。现在,我们将探讨如何让这段代码更开放,从而可以容纳任意类型的内容。然而,如果我们尝试扩展这段代码,我们会发现问题出在第 10 行声明的结构上。问题就在于此,但并非如你所想。
第 10 行中的结构问题在于,它阻碍了主结构被更广泛地使用。既然我明白这很复杂且需要时间,那么我们将继续讨论这篇文章。但我请你反复阅读这篇文章以及之前的文章,以便理解一件事。
结构体和其他类型的构造一样,都是一种表示复杂程度不一的数据的形式。解决问题的途径取决于问题的实际复杂程度。有些任务可以使用离散且简单的数据类型来解决,如浮点数表示或整数值。
当任务变得更加复杂时,离散类型可能就不够用了。在这种情况下,我们将需要更复杂的数据类型,如数组和联合体。如果问题仍然复杂,那么我们需要一种同样复杂的类型:结构体。
因此,我们在代码 01 的第 10 行声明的结构只不过是一个复杂类型。要理解我们接下来要做的事情,就必须先理解这一点。
首先,我们必须从第 04 行定义的结构的第 10 行获取此结构,同时保留代码 01。然而,这样做会使信息变得混乱,如果你之前没有理解或没有跟上之前的文章,这些信息会让你更加困惑。这是因为无法逐步实施这些变更,而必须分极少的几个步骤来完成。第一步如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. struct st_Reg 05. { 06. string h_value; 07. uint k_value; 08. }; 09. //+----------------+ 10. struct st_Data 11. { 12. //+----------------+ 13. private: 14. //+----------------+ 15. st_Reg Values[]; 16. //+----------------+ 17. public: 18. //+----------------+ 19. bool Set(const uint &arg1[], const string &arg2[]) 20. { 21. if (arg1.Size() != arg2.Size()) 22. return false; 23. 24. ArrayResize(Values, arg1.Size()); 25. for (uint c = 0; c < arg1.Size(); c++) 26. { 27. Values[c].k_value = arg1[c]; 28. Values[c].h_value = arg2[c]; 29. } 30. 31. return true; 32. } 33. //+----------------+ 34. string Get(const uint index) 35. { 36. for (uint c = 0; c < Values.Size(); c++) 37. if (Values[c].k_value == index) 38. return Values[c].h_value; 39. 40. return NULL; 41. } 42. //+----------------+ 43. }; 44. //+------------------------------------------------------------------+ 45. #define PrintX(X) Print(#X, " => ", X) 46. //+------------------------------------------------------------------+ 47. void OnStart(void) 48. { 49. const string T = "possible loss of data due to type conversion"; 50. const uint K[] = {2, 1, 4, 0, 7, 5, 3, 6}; 51. 52. st_Data info; 53. string H[]; 54. 55. StringSplit(T, ' ', H); 56. info.Set(K, H); 57. PrintX(info.Get(3)); 58. } 59. //+------------------------------------------------------------------+
代码 03
警告:为了使代码(以及我们将要进行的操作)尽可能简单明了,我们将退一步,使用字符串类型。请注意,我们正在构建的通用结构版本已不存在,执行结果仍然与图 02 相对应。
或许如果你看一下代码 03,你可能会想:“好吧,你说这很复杂,但到目前为止它仍然很简单。哪里复杂了?”那么,我们继续吧。需要更改的第二点是 Set 和 Get 函数(分别在第 19 行和第 34 行看到)不是 st_Data 的成员函数,而应该属于 st_Reg。
现在我想问:它们在 st_Data 中做什么?答案:它们使得该结构难以以更通用的方式运行,从而阻止其处理任何类型的数据。因此,我们需要从代码 03 中出现的 st_Data 结构中删除这些函数。为了更直观地展示,让我们看看修正后的片段会是什么样子。如下所示。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. struct st_Reg 05. { 06. //+----------------+ 07. private: 08. //+----------------+ 09. string h_value; 10. uint k_value; 11. //+----------------+ 12. public: 13. //+----------------+ 14. void Set(const uint arg1, const string arg2) 15. { 16. k_value = arg1; 17. h_value = arg2; 18. } 19. //+----------------+ 20. uint Get_K(void) { return k_value; } 21. //+----------------+ 22. string Get_H(void) { return h_value; } 23. //+----------------+ 24. }; 25. //+----------------+ 26. struct st_Data 27. { 28. //+----------------+ 29. private: 30. //+----------------+ 31. st_Reg Values[]; 32. //+----------------+ 33. public: 34. //+----------------+ 35. bool Set(const st_Reg &arg) 36. { 37. if (ArrayResize(Values, Values.Size() + (Values.Size() == 0 ? 2 : 1)) == INVALID_HANDLE) 38. return false; 39. 40. Values[Values.Size() - 1] = arg; 41. 42. return true; 43. } 44. //+----------------+ 45. st_Reg Get(const uint index) 46. { 47. for (uint c = 0; c < Values.Size(); c++) 48. if (Values[c].Get_K() == index) 49. return Values[c]; 50. 51. return Values[0]; 52. } 53. //+----------------+ 54. }; 55. //+------------------------------------------------------------------+ . . .
代码 04
“这简直是疯了!”冷静点,这仅仅是个开始。现在,我们有两个结构,而非一个,用于将数据组合到新的 st_Data 结构中。但在继续编写主要代码(将在 OnStart 中)之前,我们需要了解这里发生了什么。
事实上,尽管这段代码片段的工作方式与代码 03 相同,但这里有几个方面与我们习惯的方式完全不同。然而,在我看来,这仍然是初级水平的代码,因为我们处理的是非常简单的内容。
首先,需要记住的是,st_Reg 是一个设计用于结构化代码的结构。换言之,我们不会直接与变量交互,而是会创建一个可以管理它们的上下文。
本质上,st_Reg 的工作方式与我们开始讨论嵌套结构之前的方式相同。现在开始的部分对许多初学者来说会变得复杂,尤其是当他们对变量的基本概念以及如何处理数据类型不明确时。让我们来了解一下第二个结构体(称为 st_Data)中发生了什么。由于 st_Reg 是一个复杂的数据类型,在第 31 行我们可以声明一个变量,在本例中是一个数组,来存储该类型的数据,即 st_Reg。目前为止,你可能没发现任何问题,对吗?
然而,如果我们看一下第 35 行,就会发现事情开始失去意义,尤其是如果我们注意第 37 行的话。我的天哪,那里发生了什么事?!亲爱的读者,在这一行中,我们只是简单地尝试分配内存空间。然而,有一个小细节让事情变得稍微复杂一些。事实是,在 MQL5 中,我们不能返回指针。因此,在第 45 行所示的函数中,存在一个小缺陷,我们正在第 37 行尝试解决该缺陷。
所以,请注意,如果我们尝试查找插入到 st_Data 结构中的值但找不到它,则必须返回一个无效值。在 C 和 C++ 等语言中,我们通常通过返回 null 值来实现这一点。然而,在 MQL5 中,我们无法做到这一点,我们需要一个小技巧。因此,在第 37 行中,当我们尝试分配第一个块以添加 st_Reg 类型的数据时,我们分配了两个内存单元。第一个将是空的。但是,第二个将被赋予与第 40 行对应的值。
由于这是第一个元素,因此将为其分配两个位置。从下一次调用开始,第 37 行将判断是否已分配至少两个元素,若已分配,则仅再分配一个。因此,每次对 st_Data 结构的 Set 函数进行新调用时,都会发生这种情况。请注意,这与 st_Reg 结构的 Set 过程无关,其中原因稍后将进行讨论。
因此,由于 st_Data 结构的 Get 函数与 Set 结构的解释直接相关,我们只需要理解 Get 函数接收的参数值并不表示正在 Values 数组中搜索的索引,而是表示该特定元素中实际存储的值。因此,我们需要一个循环来在 st_Reg 结构中查找特定值。我们稍后会讨论这个问题。总的来说,就是这样。
现在我们可以查看主代码了。如果你还记得,我们在代码 04 的片段中看到的代码完全可以放在一个头文件中,而且不会出现任何问题。但在展示如何实现这一步骤以使代码通用化之前,我们还需要查看代码的其他部分。如下所示。如下所示。
. . . 55. //+------------------------------------------------------------------+ 56. #define PrintX(X) Print(#X, " => [", X, "]") 57. //+------------------------------------------------------------------+ 58. void OnStart(void) 59. { 60. const string T = "possible loss of data due to type conversion"; 61. const uint K[] = {2, 1, 4, 0, 7, 5, 3, 6}; 62. 63. st_Data info; 64. string H[]; 65. 66. StringSplit(T, ' ', H); 67. for (uint c = 0; c < H.Size(); c++) 68. { 69. st_Reg reg; 70. 71. reg.Set(K[c], H[c]); 72. info.Set(reg); 73. } 74. 75. PrintX(info.Get(3).Get_H()); 76. PrintX(info.Get(13).Get_H()); 77. } 78. //+------------------------------------------------------------------+
代码 05
于是,最有趣、最激动人心的部分开始了。鉴于之前已经展示过这段代码,你可能已经理解了其中的大部分内容,接下来让我们继续学习新的部分。这具体涉及第 67 行的循环,以及第 75 行和第 76 行。
我们先从循环开始。由于要放入 st_Data 结构中的每个元素都是 st_Reg 类型,因此我们需要第 71 行创建一个 st_Reg 结构,以便我们可以使用第 72 行将 st_Reg 类型的元素存储在 st_Data 结构中。现在的问题是:为什么我们不在 st_Data 结构中直接这样做?答案是,如果直接把 stReg 类型的元素加入 st_Data 结构,那么我们以后就无法扩展这个结构了。但别担心,你很快就会明白的。
在将所有元素写入 st_Data 结构后,我们可以使用第 75 行和第 76 行来测试这个结构。为此,在第 75 行中,我们搜索索引为 3 的字符串。而且,查看第 60 行和第 61 行,你可以看到这个索引的值是什么。在第 76 行,将搜索索引为 13 的元素。由于第 61 行未定义此索引,因此返回的值将是代码 04 的第 51 行定义的值。
有了这些初步知识,我们就可以运行代码了。因此,我们将看到以下结果:

图 03
换句话说,它工作得很好。现在我们可以对代码中的很大一部分进行泛化,但为了做到这一点,我们需要转向一个新的主题。这样,我们就可以冷静地研究我们已经完成的工作,然后再尝试理解,为了完成这些泛化还需要做些什么。
结构的结构,第三部分:复仇
如果你觉得上一个话题中的信息令人困惑,那么请做好准备,因为接下来的内容将会更加引人入胜。在这里,情况确实变得更加复杂。所以,请留意以下事实。在上一主题的代码 04 中,st_Data 结构只能接受一种数据类型,即 st_Reg 类型,但这并不能使其具有通用性。这使 stData 只能处理由 st_Reg 所定义的那类内容,即使它们之间没有相互关联。
然而,如果我们稍微修改一下 st_Data 结构的代码,就可以放开它的限制,使其能够处理不同的数据类型。注意我刚才说的话。当 st_Data 结构成为模板时,它不会完全脱离 st_Reg 结构,因为 st_Reg 结构与 Get 函数相关,而 Get 函数仍与 st_Reg 类型绑定。尽管如此,这一努力仍是值得的。
如上所述,我们需要做的就是将 st_Data 结构转换为模板,并同时调整主代码以匹配编译器必须生成的模板。这是通过以下代码实现的。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. struct st_Reg 05. { 06. //+----------------+ 07. private: 08. //+----------------+ 09. string h_value; 10. uint k_value; 11. //+----------------+ 12. public: 13. //+----------------+ 14. void Set(const uint arg1, const string arg2) 15. { 16. k_value = arg1; 17. h_value = arg2; 18. } 19. //+----------------+ 20. uint Get_K(void) { return k_value; } 21. //+----------------+ 22. string Get_H(void) { return h_value; } 23. //+----------------+ 24. }; 25. //+----------------+ 26. template <typename T> 27. struct st_Data 28. { 29. //+----------------+ 30. private: 31. //+----------------+ 32. T Values[]; 33. //+----------------+ 34. public: 35. //+----------------+ 36. bool Set(const T &arg) 37. { 38. if (ArrayResize(Values, Values.Size() + (Values.Size() == 0 ? 2 : 1)) == INVALID_HANDLE) 39. return false; 40. 41. Values[Values.Size() - 1] = arg; 42. 43. return true; 44. } 45. //+----------------+ 46. T Get(const uint index) 47. { 48. for (uint c = 0; c < Values.Size(); c++) 49. if (Values[c].Get_K() == index) 50. return Values[c]; 51. 52. return Values[0]; 53. } 54. //+----------------+ 55. }; 56. //+------------------------------------------------------------------+ 57. #define PrintX(X) Print(#X, " => [", X, "]") 58. //+------------------------------------------------------------------+ 59. void OnStart(void) 60. { 61. const string T = "possible loss of data due to type conversion"; 62. const uint K[] = {2, 1, 4, 0, 7, 5, 3, 6}; 63. 64. st_Data <st_Reg> Info_1; 65. string H[]; 66. 67. StringSplit(T, ' ', H); 68. for (uint c = 0; c < H.Size(); c++) 69. { 70. st_Reg reg; 71. 72. reg.Set(K[c], H[c]); 73. Info_1.Set(reg); 74. } 75. 76. PrintX(Info_1.Get(3).Get_H()); 77. PrintX(Info_1.Get(13).Get_H()); 78. } 79. //+------------------------------------------------------------------+
代码 06
从理论上讲,代码 06 可以在 st_Data 结构中包含任何类型的数据结构或任何数据类型。理论上来说,这是正确的。原因就在于代码 06 第 46 行的 Get 函数。为了使代码更具通用性,我们只需添加第 26 行,并将第 32、36和 46 行中对 st_Reg 的引用替换为类型 T。之后,我们需要修改第 64 行的声明,以便编译器能够生成相应的结构。
执行代码 06 的结果与执行代码 05 的结果相同,即图 03。然而,从某种意义上说,我们仍然与 st_Reg 结构有联系,尽管这种联系(这也是一个问题)也可以带来一些好处。这完全取决于每个读者对特定主题的需求、创造力水平和知识水平。请注意以下几点。st_Data 和 st_Reg 结构之间唯一剩下的联系是所谓的 Get_K 函数,位于第 49 行。这是唯一仍然存在的联系。由于 st_Reg 结构也可以重载,这样一来,只要提供同样的接口,就可以配合不同的类型使用。
但由于这些内容可能会在文章的最后部分引出难以理解的复杂细节,我们现在将不对其进行讨论。
然而,即便如此,我们还是可以做一些我觉得有趣的事情,而且这些事情在文章的其余部分也能得到解释。它涉及代码 06 的第 49 行。亲爱的读者,现在请注意:在文章“ 从初级到中级:重载”中,我们讨论了一种非常简单且有趣的使用函数和过程重载的方法。在本系列的其他文章中,我们还展示了如何使用与标准库中相同的名称来实现这一目标。
现在,有趣的部分来了:如果在代码 06 中,我们创建一种方法来重载第 49 行所示的 Get_K 函数,会怎么样呢?这会带来哪些可能性?正如我们之前所说,这正是创造力和概念应用让事情变得非常有趣的地方。这是因为,如果你仔细观察,你会发现 st_Reg 结构(如代码 06 所示)只包含两个变量。但是我们可以重载 st_Reg 结构,以获得具有更多变量甚至完全不同数据类型的另一个结构。
然而,这样做的话,我们并不一定需要删除原始的 st_Reg 结构,或者修改已在代码 06 中创建的 st_Data 结构。由于我不确定你是否理解我的意思,让我们来看一个代码片段,这样你就能明白我想展示的内容了。以下是代码。
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. //+------------------------------------------------------------------+
代码 07
执行代码 07 时,终端中会出现一个与下图非常相似的结果。

图 05
我的天啊!我们创造出了多么疯狂而又奇妙的东西啊!亲爱的读者们,请冷静,我们感兴趣的是图 05 中突出显示的信息,该图由第 89 行的过程生成,我们将重点解释该信息,因为大部分代码都很容易理解。理解第 89 行定义的过程对于掌握下一篇文章将要介绍的内容至关重要。
但在解释第 89 行过程中发生的情况之前,我们需要了解一些其他要点。让我们从第 26 行开始,这一行创建了一个新的结构体。值得注意的是,它与 st_Reg 结构有着相似的基础,但相似之处仅此而已,因为 st_Bio 结构在其上下文中包含更多的变量和一组不同的操作。对我们来说,重要的是第 44 行的函数。这是因为它与第 20 行执行相同的功能,因为它们具有相同的目标和目的。这对我们接下来要做的事情很重要。
另请注意,代码 07 中的 st_Data 结构与代码 06 中的 st_Reg 结构完全相同。唯一真正的变化是 st_Bio 结构的出现。在此之后,我们可以继续讨论主要的 OnStart 过程,这是我们代码的入口点。请注意,我们在这里添加了一些代码 06 中没有的额外元素。这些示例正是为了展示,如果代码的实现阶段经过深思熟虑,st_Data 结构可以变得多么灵活。
基本上,我们添加了第 107 行和第 111 行。现在,请集中注意力。请注意,第 110 行和第 111 行之间的区别正是将放入 st_Data 结构中的数据类型。因此,在第 116 行的循环内部,我们在第 119 行添加了一个新变量,该变量将在第 122 行接收值,在第 125 行,我们将数据赋值或添加到结构中。在这种情况下,数据类型将与第 124 行中的数据类型不同。到目前为止,一切都如代码 06 所预测的那样发生了。
然而,这就是复杂性的开始,同时也是更有趣的部分:我们有第 130 行,它将导致执行第 89 行的程序。现在让我们继续讨论最有趣的部分。请注意,我们关注的是第 94 行的循环内部,而最让我们感兴趣的是第 97 行,我们应该将其与第 128 行进行比较。它们之间有什么区别?实际上没有区别,只是由于第 76 行返回了一个结构类型,该类型引用了第 62 行声明的变量的数据类型,所以这两行之间没有差异。
在文章“从基础到中级:变量(三)中,我们解释了函数是一种特殊类型的变量。在这里,这个概念以一种非常有趣的方式得到了应用,因为根据我们从第 76 行函数中返回的结果,我们就能够转而操作某一种具体的数据类型,在本例中是指向一个结构体。如果没有结构化编程,几乎不可能(或者极其困难)创造出这里正在做的事情。关键在于第 97 行使用了对 st_Bio 结构的引用。在第 128 行,我们指向 st_Reg 结构,由于这两个结构都在 st_Data 结构中,我们可以用相同的方式处理不同类型的信息或数据集。
要理解这里真正发生的情况,你需要进行实践和实验。如果你几乎没有编程经验,仅仅通过查看代码是无法理解的。记住:我们仍在使用的材料,在我看来,还处于初级水平。
最后的想法
在今天的文章中,我们展示了如何通过对问题进行结构化处理并创造一个更简单、更吸引人的解决方案来解决问题。虽然这里提供的材料是教育性的,因此不是生产代码,但彻底掌握这里提出的概念和知识至关重要。因此,在未来,你将能够理解我们将展示的代码,因为随着我们的深入,我们将能够揭示编程中更深层、更有趣的细节,而如果我们必须解释每一行代码,这将是不可能的。
因此,借此机会,请借此机会,对索引页中的相关内容认真学习,不要急于求成。将这些知识学习并应用于实践,因为在下一篇文章中,我们将从今天中断的地方继续。在继续讨论面向对象编程之前,我们还没有完成对结构化编程以及如何利用结构化编程的讨论。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/15912
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
新手在交易中的10个基本错误
神经网络在交易中的应用:用于智能体自适应行为的分层技能发现(HiSSD)