从基础到中级:继承
概述
在上一篇文章《从基础到中级:结构(七)》中,我们开始探讨一个结构化的程序,虽然乍一看它可能相当复杂,但最终会证明它非常吸引人且有趣。我之所以知道这一点,是因为在我编程之旅的初期,我发现很难理解为什么有些代码起作用,而有些却不能。经过多年的奋斗和坚持,我终于领悟到了我在这些文章中试图传达给你们的那些概念。许多人认为,通过参加课程或仅仅研究其他程序员的代码,就能学会编程。
然而,不幸的是,要想真正学会编程,练习是必不可少的。只有经过一段时间,解决了一个又一个的问题,您才能成为一个优秀的专业人士。但是,我再说一遍,这需要时间。然而,如果我能稍微加快这个过程,为什么不把这些知识分享给那些真正想学习的人呢?好了,我们已经到达了一个关键点。
接下来,事情会变得更加有趣。打下坚实基础并理解前几篇文章中解释的所有内容,这一点非常重要。如果你没有将之前所学付诸实践,你会发现很难跟上本文及后续文章的节奏。
简单继承
最常见的误解之一是,认为继承只存在于类代码中,或者如常说的那样,只存在于面向对象编程中。那么,什么是继承?简而言之,就是创建一个简单的数据类型,并基于此创建其他更复杂的数据类型,而无需创建已在那个更简单的数据类型中实现的元素、函数和过程。
为了更好地理解,让我们考虑以下情况:自然界中有许多种动物、植物和矿物。假设我们需要编写代码,将所有输入数据按照自然界中的类别进行整理,在本例中,我们将这些类别指定为动物、植物和矿物。你会怎么做?最有可能的是,你需要创建三种不同的结构来将输入数据划分为这三种预定义类型之一。
通过构建这样的结构,你将意识到,遵循完全结构化的编程风格,可以使代码更易于操作和理解。这一点在之前的文章中已经有所提及。然而,动物和植物有一些共同之处。两者都是生物体,与矿物不同。因此,植物和动物都拥有相同的结构元素。
这些元素可以被移除并放到一个新的基础结构中。这样,我们就把生物共有的部分抽取了出来,这将避免数据结构内部的代码重复,并简化其未来的维护、实现和改进。
通常,大多数面向金融市场工作的代码(使用 MetaTrader 5,因此也使用 MQL5)并不需要我在本文中介绍的抽象层次。就我们的目的而言,也不需要使用结构化编程或面向对象编程。
然而,鉴于编程的本质以及我们可能遇到的问题类型,理解我在此介绍的机制和概念对你来说极其有用且必要。这并不是说你需要它们,而是为了更轻松地工作,你需要了解某些事情是如何运作的。为了实现我们的目标,尽管这些概念看似复杂,但理解它们将极大地简化未来的工作。
我重申,要创建 EA 或指标,并不需要理解结构化编程,因为这可以通过传统编程来实现。如果你对我的话有疑问,可以查看我的第一篇文章:«从零开始开发交易 EA»。在本文中,我们未使用任何结构化或面向对象的编程技术,而是探讨了如何创建一个简单的 EA,该 EA 能够直接向图表发送订单。
这一点同样适用于指标。在文章《从基础到中级:指标(四)》中,我们展示了如何在图表上显示指标。所有这些都很简单,不需要特殊的知识。要创建所需的应用程序,您只需具备常识并了解 MQL5 文档即可。
一旦明确了编写这些程序真正需要掌握的内容与那些并非必须知道的内容之间的区别,我们就可以回到之前那个假设的例子。既然我们知道生物和非生物之间存在共性,那么我们就可以在实现方式以及功能和程序的易用性和维护性方面,将它们逻辑且一致地分开。
为了更清楚地说明这一点,让我们来看上一篇文章中讨论的一个代码。下面可以看到类似的结果。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. struct st_Reg 005. { 006. //+----------------+ 007. private: 008. //+----------------+ 009. string h_value; 010. uint k_value; 011. //+----------------+ 012. public: 013. //+----------------+ 014. void Set(const uint arg1, const string arg2) 015. { 016. k_value = arg1; 017. h_value = arg2; 018. } 019. //+----------------+ 020. uint Get_K(void) { return k_value; } 021. //+----------------+ 022. string Get_H(void) { return h_value; } 023. //+----------------+ 024. }; 025. //+------------------------------------------------------------------+ 026. struct st_Bio 027. { 028. //+----------------+ 029. private: 030. //+----------------+ 031. string h_value; 032. string b_value; 033. uint k_value; 034. //+----------------+ 035. public: 036. //+----------------+ 037. void Set(const uint arg1, const string arg2, const string arg3) 038. { 039. k_value = arg1; 040. h_value = arg2; 041. b_value = arg3; 042. } 043. //+----------------+ 044. uint Get_K(void) { return k_value; } 045. //+----------------+ 046. bool Get_Bio(string &arg1, string &arg2) 047. { 048. arg1 = h_value; 049. arg2 = b_value; 050. 051. return true; 052. } 053. //+----------------+ 054. }; 055. //+------------------------------------------------------------------+ 056. template <typename T> 057. struct st_Data 058. { 059. //+----------------+ 060. private: 061. //+----------------+ 062. T Values[]; 063. //+----------------+ 064. public: 065. //+----------------+ 066. bool Set(const T &arg) 067. { 068. if (ArrayResize(Values, Values.Size() + (Values.Size() == 0 ? 2 : 1)) == INVALID_HANDLE) 069. return false; 070. 071. Values[Values.Size() - 1] = arg; 072. 073. return true; 074. } 075. //+----------------+ 076. T Get(const uint index) 077. { 078. for (uint c = 0; c < Values.Size(); c++) 079. if (Values[c].Get_K() == index) 080. return Values[c]; 081. 082. return Values[0]; 083. } 084. //+----------------+ 085. }; 086. //+------------------------------------------------------------------+ 087. #define PrintX(X) Print(#X, " => [", X, "]") 088. //+------------------------------------------------------------------+ 089. void CheckBio(st_Data <st_Bio> &arg) 090. { 091. string sz[2]; 092. 093. Print("Checking data in the structure..."); 094. for (uint i = 7; i < 11; i += 3) 095. { 096. Print("Index: ", i, " Result: "); 097. if (arg.Get(i).Get_Bio(sz[0], sz[1])) 098. ArrayPrint(sz); 099. else 100. Print("Failed."); 101. } 102. } 103. //+------------------------------------------------------------------+ 104. void OnStart(void) 105. { 106. const string T = "possible loss of data due to type conversion"; 107. const string M[] = {"2", "cool", "4", "zero", "mad", "five", "what", "xoxo"}; 108. const uint K[] = {2, 1, 4, 0, 7, 5, 3, 6}; 109. 110. st_Data <st_Reg> Info_1; 111. st_Data <st_Bio> Info_2; 112. 113. string H[]; 114. 115. StringSplit(T, ' ', H); 116. for (uint c = 0; c < H.Size(); c++) 117. { 118. st_Reg reg; 119. st_Bio bio; 120. 121. reg.Set(K[c], H[c]); 122. bio.Set(K[c], M[c], H[c]); 123. 124. Info_1.Set(reg); 125. Info_2.Set(bio); 126. } 127. 128. PrintX(Info_1.Get(3).Get_H()); 129. PrintX(Info_1.Get(13).Get_H()); 130. CheckBio(Info_2); 131. } 132. //+------------------------------------------------------------------+
代码 01
执行代码 01 时,我们将得到下图所示的结果。

图 01
现在让我们来谈谈问题的核心。正如动植物具有共性元素一样,在代码 01 中,k_value 是 st_Reg 和 st_Bio 的共性元素。然而,它们彼此独立,是两种完全不同的结构。这在下面有直观展示。

图 02
换言之,这是两个不同的实体,但它们具有共同的特征。当多个结构共享以相同方式访问和处理的元素时,就会出现这种代码重复,因为在这种情况下,没有必要分别重复定义它们。这只会以一种完全不必要的方式使代码变得复杂。
为了更清楚地说明这一点,想象一下,由于某种原因,你决定改进 st_Reg 结构中的 Get_K 函数。因此,你注意到通用代码有了显著改进,但在 st_Bio 中却没有这样做。当同时使用这两种结构时,你会发现尽管 st_Bio 使用了 Get_K,但其行为与 st_Reg 不同。
问题就从这里开始。st_Reg 结构中的 Get_K 代码被复制到 st_Bio 结构中,一段时间后,Get_K 代码需要再次更改,这意味着需要在两个结构上执行此操作。
这样的行为不仅浪费时间,还使得代码难以改进。而在这里,我们只使用了两个结构。在实际代码中,可能会有数十甚至数百个结构体共享某些共同点。你能想象调试和维护这样的代码会是什么样子吗?一场噩梦。然而,有一个简单的方法可以简化所有这些工作。这个方法就是使用继承。
继承只是将这两个结构中的共同部分移出,并将其放入另一个结构中。然后,以某种方式,使该结构能够使用它继承的代码。我知道这看起来很复杂。但实际上,你会发现这比你想象的要容易得多。
现在让我们在实践中应用这一点,使代码 01 使用继承。为了实现我们的目标,第一步如下面的代码片段所示。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. struct st_Base 05. { 06. private: 07. uint KeyValue; 08. public: 09. //+----------------+ 10. void SetKey(const uint arg) { KeyValue = arg; } 11. //+----------------+ 12. uint GetKey(void) { return KeyValue; } 13. }; 14. //+------------------------------------------------------------------+ 15. struct st_Reg 16. { 17. private: 18. string Value; 19. public: 20. //+----------------+ 21. void SetValue(const uint arg1, const string arg2) 22. { 23. Value = arg2; 24. } 25. //+----------------+ 26. string GetValue(void) { return Value; } 27. }; 28. //+------------------------------------------------------------------+ 29. struct st_Bio 30. { 31. private: 32. string Value[2]; 33. public: 34. //+----------------+ 35. void SetValue(const uint arg1, const string arg2, const string arg3) 36. { 37. Value[0] = arg2; 38. Value[1] = arg3; 39. } 40. //+----------------+ 41. bool GetValue(string &arg1, string &arg2) 42. { 43. arg1 = Value[0]; 44. arg2 = Value[1]; 45. 46. return true; 47. } 48. //+----------------+ 49. }; 50. //+------------------------------------------------------------------+ . . .
代码 02
在代码 02 的代码片段中,我们可以看到创建了一个新的结构。它被称为 st_Base。现在要注意了,尽管我们已改进了这段代码(代码 02)中变量的组织方式,但它仍然包含与代码 01 相同数量和类型的变量。然而,它的组织结构更为合理。但这里最重要的本质上是 st_Base 结构。之后,我们得到以下结果。

图 03
请注意,它与图 02 非常相似。但是,图 03 没有使用继承。这是因为代码片段 02 也没有使用它。因此,如果我们尝试使用代码片段 02 编译代码 01,编译器将生成几个错误。为了解决这个问题,我们需要将图 03 转换为图 04,如下所示。

图 04
在图 04 中,箭头指示继承的实现方式。换言之,我们打算在不大幅修改代码的情况下,将 st_Base 结构的内容纳入 st_Bio 和 st_Reg 结构中。为此,我们只需将代码 02 修改为下面所示的代码。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. struct st_Base 05. { 06. private: 07. uint KeyValue; 08. public: 09. //+----------------+ 10. void SetKey(const uint arg) { KeyValue = arg; } 11. //+----------------+ 12. uint GetKey(void) { return KeyValue; } 13. }; 14. //+------------------------------------------------------------------+ 15. struct st_Reg : public st_Base 16. { 17. private: 18. string Value; 19. public: 20. //+----------------+ 21. void SetValue(const uint arg1, const string arg2) 22. { 23. Value = arg2; 24. } 25. //+----------------+ 26. string GetValue(void) { return Value; } 27. }; 28. //+------------------------------------------------------------------+ 29. struct st_Bio : public st_Base 30. { 31. private: 32. string Value[2]; 33. public: 34. //+----------------+ 35. void SetValue(const uint arg1, const string arg2, const string arg3) 36. { 37. Value[0] = arg2; 38. Value[1] = arg3; 39. } 40. //+----------------+ 41. bool GetValue(string &arg1, string &arg2) 42. { 43. arg1 = Value[0]; 44. arg2 = Value[1]; 45. 46. return true; 47. } 48. //+----------------+ 49. }; 50. //+------------------------------------------------------------------+ . . .
代码 03
看看在代码中实现继承有多么困难。然而,如代码片段 03 所示,存在对尚未解析的值的引用。这是因为在这个代码片段中,我们只实现了继承。我们尚未在实际应用中以一种能应用 KeyValue 信息的方式使用它。我们的目标是让 st_Data 在数据之间建立连接,以构建图 01 中所示的内容。
为了解决这些问题,我们需要再次修改代码。结果,它看起来会是这样的:
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. struct st_Base 005. { 006. private: 007. uint KeyValue; 008. public: 009. //+----------------+ 010. void SetKey(const uint arg) { KeyValue = arg; } 011. //+----------------+ 012. uint GetKey(void) { return KeyValue; } 013. }; 014. //+------------------------------------------------------------------+ 015. struct st_Reg : public st_Base 016. { 017. private: 018. string Value; 019. public: 020. //+----------------+ 021. void SetValue(const uint arg1, const string arg2) 022. { 023. SetKey(arg1); 024. Value = arg2; 025. } 026. //+----------------+ 027. string GetValue(void) { return Value; } 028. }; 029. //+------------------------------------------------------------------+ 030. struct st_Bio : public st_Base 031. { 032. private: 033. string Value[2]; 034. public: 035. //+----------------+ 036. void SetValue(const uint arg1, const string arg2, const string arg3) 037. { 038. SetKey(arg1); 039. Value[0] = arg2; 040. Value[1] = arg3; 041. } 042. //+----------------+ 043. bool GetValue(string &arg1, string &arg2) 044. { 045. arg1 = Value[0]; 046. arg2 = Value[1]; 047. 048. return true; 049. } 050. //+----------------+ 051. }; 052. //+------------------------------------------------------------------+ 053. template <typename T> 054. struct st_Data 055. { 056. private: 057. T Values[]; 058. public: 059. //+----------------+ 060. bool Set(const T &arg) 061. { 062. if (ArrayResize(Values, Values.Size() + (Values.Size() == 0 ? 2 : 1)) == INVALID_HANDLE) 063. return false; 064. 065. Values[Values.Size() - 1] = arg; 066. 067. return true; 068. } 069. //+----------------+ 070. T Get(const uint index) 071. { 072. for (uint c = 0; c < Values.Size(); c++) 073. if (Values[c].GetKey() == index) 074. return Values[c]; 075. 076. return Values[0]; 077. } 078. }; 079. //+------------------------------------------------------------------+ 080. #define PrintX(X) Print(#X, " => [", X, "]") 081. //+------------------------------------------------------------------+ 082. void CheckBio(st_Data <st_Bio> &arg) 083. { 084. string sz[2]; 085. 086. Print("Checking data in the structure..."); 087. for (uint i = 7; i < 11; i += 3) 088. { 089. Print("Index: ", i, " Result: "); 090. if (arg.Get(i).GetValue(sz[0], sz[1])) 091. ArrayPrint(sz); 092. else 093. Print("Failed."); 094. } 095. } 096. //+------------------------------------------------------------------+ 097. void OnStart(void) 098. { 099. const string T = "possible loss of data due to type conversion"; 100. const string M[] = {"2", "cool", "4", "zero", "mad", "five", "what", "xoxo"}; 101. const uint K[] = {2, 1, 4, 0, 7, 5, 3, 6}; 102. 103. st_Data <st_Reg> Info_1; 104. st_Data <st_Bio> Info_2; 105. 106. string H[]; 107. 108. StringSplit(T, ' ', H); 109. for (uint c = 0; c < H.Size(); c++) 110. { 111. st_Reg reg; 112. st_Bio bio; 113. 114. reg.SetValue(K[c], H[c]); 115. bio.SetValue(K[c], M[c], H[c]); 116. 117. Info_1.Set(reg); 118. Info_2.Set(bio); 119. } 120. 121. PrintX(Info_1.Get(3).GetValue()); 122. PrintX(Info_1.Get(13).GetValue()); 123. CheckBio(Info_2); 124. } 125. //+------------------------------------------------------------------+
代码 04
代码 04 与代码 01 具有相同的效果。但是,在代码 04 中,我们使用了结构之间的继承。因此,我们对 st_Base 结构所做的任何改进都会自动惠及整个代码。换句话说,对 st_Base 结构所做的任何更改,无论多么微小,都会自动提供给 st_Reg 和 st_Bio。
当然,前提是该过程、函数或变量包含在代码的公共部分。当然,这仅适用于通过公共接口暴露的成员。如果 stBase 中的某个成员不可公开访问,那么 stReg 和 stBio 将无法直接使用它。
我们在代码 04 中未提及但可能很重要的一点是,在第 15 行和第 30 行,继承是公共的。这使得任何一种结构类型的变量都能访问 st_Base 的公共成员,从而也能访问 st_Base 结构的任何公共元素。如果我们在那些代码行中使用的不是保留字 public,而是 private,那么任何使用该结构的变量,无论是 st_Bio 还是 st_Reg,都无法访问 st_Base 结构的内容。我建议你稍后用附录中的文件来尝试,以便更好地理解。
既然本文的主要主题是继承,让我们用更轻松一点的方式来试试这个主题。为此,我们将转入一个新的话题。我不想让你害怕,以为我要给你看的东西只有资深专家才能做到。事实上,情况并非如此。正在学习并关注这一系列文章的你们,也可以做类似的事情。
然而,要做到这一点,必须应用之前文章中介绍过的某些概念。现在,让我们继续我们的睡衣派对,因为乐趣才刚刚开始。
结构体、模板和继承:一种热闹的组合
许多人并不了解 MQL5 的功能,尽管它并不具备 C 和 C++ 的所有功能。这是因为,与 C 和 C++ 相比,MQL5 编程要简单得多。如果这些文章是关于 C 和 C++ 的,我想你们中的很多人早就放弃了。但由于这里我们只专注于 MQL5 编程,所以文章大多会包含非常简单的内容。然而,由于它们很少被研究,结果反而显得近乎神秘。
其中一个因素就是我们将迄今为止观察到的一切结合起来。换言之,我们能否将结构化编程与模板和继承的功能结合起来,在单一代码中实现?是的,亲爱的读者,尽管这些材料可能看起来很复杂,但对我来说,它仍然相当简单,只是比我之前展示的所有内容稍微复杂一点。
然而,如果你学习和反思我们所学过的概念,你最终会思考如何将所有这些结合起来,编写出真正有趣的代码,因为我们可以利用这些概念,而许多人需要编写大量代码才能达到同样的效果。但我们只需使用非常简单的代码,将所有工作都交给编译器来完成。
由于我不想在同时使用这些资源时,用上一节中的代码吓到你,我们将逐步开始。这样,你将逐渐习惯这些想法,并理解这些概念的重要性,而不是试图死记硬背和复制随机代码,以便日后使用。
所以,我们的代码将这样开始:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. struct st_Base 05. { 06. private : 07. uint KeyValue; 08. public : 09. //+----------------+ 10. void SetKey(const uint arg) 11. { 12. KeyValue = arg; 13. } 14. //+----------------+ 15. uint GetKey(void) 16. { 17. return KeyValue; 18. } 19. //+----------------+ 20. }; 21. //+------------------------------------------------------------------+
代码 05
好了,我想大家都能理解代码 05。然后我们就可以进入下一阶段了。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. struct st_Base 05. { 06. private : 07. uint KeyValue; 08. public : 09. //+----------------+ 10. void SetKey(const uint arg) 11. { 12. KeyValue = arg; 13. } 14. //+----------------+ 15. uint GetKey(void) 16. { 17. return KeyValue; 18. } 19. //+----------------+ 20. }; 21. //+------------------------------------------------------------------+ 22. struct st_Dev01 : public st_Base 23. { 24. private : 25. string ValueInfo; 26. public : 27. //+----------------+ 28. void SetKeyInfo(uint arg1, string arg2) 29. { 30. SetKey(arg1); 31. ValueInfo = arg2; 32. } 33. //+----------------+ 34. string GetInfo(void) 35. { 36. return ValueInfo; 37. } 38. //+----------------+ 39. } 40. //+------------------------------------------------------------------+
代码 06
太好了,我们现在已经实现了继承机制。我们有两个非常简单且相当基本的结构。现在是时候进行下一步了。在这个阶段,我们将实现一个可被视为列表的结构。这也很简单,如下所示。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. struct st_Base 05. { 06. private : 07. uint KeyValue; 08. public : 09. //+----------------+ 10. void SetKey(const uint arg) 11. { 12. KeyValue = arg; 13. } 14. //+----------------+ 15. uint GetKey(void) 16. { 17. return KeyValue; 18. } 19. //+----------------+ 20. }; 21. //+------------------------------------------------------------------+ 22. struct st_Dev01 : public st_Base 23. { 24. private : 25. string ValueInfo; 26. public : 27. //+----------------+ 28. void SetKeyInfo(uint arg1, string arg2) 29. { 30. SetKey(arg1); 31. ValueInfo = arg2; 32. } 33. //+----------------+ 34. string GetInfo(void) 35. { 36. return ValueInfo; 37. } 38. //+----------------+ 39. }; 40. //+------------------------------------------------------------------+ 41. struct st_List 42. { 43. private : 44. st_Dev01 List[]; 45. uint nElements; 46. public : 47. //+----------------+ 48. void Clear(void) 49. { 50. ArrayFree(List); 51. nElements = 0; 52. } 53. //+----------------+ 54. bool AddList(const st_Dev01 &arg) 55. { 56. nElements += (nElements == 0 ? 2 : 1); 57. ArrayResize(List, nElements); 58. List[nElements - 1] = arg; 59. 60. return true; 61. 62. } 63. //+----------------+ 64. st_Dev01 SearchKey(const uint arg) 65. { 66. for (uint c = 1; c < nElements; c++) 67. if (List[c].GetKey() == arg) 68. return List[c]; 69. 70. return List[0]; 71. } 72. //+----------------+ 73. }; 74. //+------------------------------------------------------------------+
代码 07
如你所见,与我们之前所做的相比,我们没有做任何改变。一切都保持着简单且具有教育意义。现在,我们将创建一个简短的数据列表,以确保一切正常工作。我们将在下面的代码中实现这一点。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "Tutorial\File 01.mqh" 05. //+------------------------------------------------------------------+ 06. #define PrintX(X) Print(#X, " => [", X, "]") 07. //+------------------------------------------------------------------+ 08. void OnStart(void) 09. { 10. const string Names[] = 11. { 12. "Daniel Jose", 13. "Edimarcos Alcantra", 14. "Carlos Almeida", 15. "Yara Alves" 16. }; 17. 18. st_List list; 19. 20. for (uint c = 0; c < Names.Size(); c++) 21. { 22. st_Dev01 info; 23. 24. info.SetKeyInfo(c, Names[c]); 25. list.AddList(info); 26. } 27. 28. PrintX(list.SearchKey(2).GetInfo()); 29. } 30. //+------------------------------------------------------------------+
代码 08
请注意,代码 07 中的所有代码实际上都是一个头文件。它被添加到代码 08 的第 04 行。在第 10 行,我们创建了一个包含几个名字的小数组。在第 18 行,定义了一个数据结构变量。在第 25 行,我们创建了结构体本身。在第 28 行,我们使用键来搜索与该特定键相关的信息。执行代码后,我们得到了如下所示的结果。

图 05
总之,没什么特别的。一切都符合预期,也符合迄今为止所展示和研究的内容。但现在,是时候展示一些新东西了,真正有趣的部分。新颖之处在于,我们现在将把重载功能纳入这个列表系统中。为了尽可能保持流程顺畅,我们会先从较后面的部分讲起,再一步步往前推。因此,我们将修改头文件代码,使其看起来如下所示。让我们回顾一下,原始头文件代码是代码 07。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. struct st_Base 05. { 06. private : 07. uint KeyValue; 08. public : 09. //+----------------+ 10. void SetKey(const uint arg) 11. { 12. KeyValue = arg; 13. } 14. //+----------------+ 15. uint GetKey(void) 16. { 17. return KeyValue; 18. } 19. //+----------------+ 20. }; 21. //+------------------------------------------------------------------+ 22. struct st_Dev01 : public st_Base 23. { 24. private : 25. string ValueInfo; 26. public : 27. //+----------------+ 28. void SetKeyInfo(uint arg1, string arg2) 29. { 30. SetKey(arg1); 31. ValueInfo = arg2; 32. } 33. //+----------------+ 34. string GetInfo(void) 35. { 36. return ValueInfo; 37. } 38. //+----------------+ 39. }; 40. //+------------------------------------------------------------------+ 41. template <typename T> 42. struct st_List 43. { 44. private : 45. T List[]; 46. uint nElements; 47. public : 48. //+----------------+ 49. void Clear(void) 50. { 51. ArrayFree(List); 52. nElements = 0; 53. } 54. //+----------------+ 55. bool AddList(const T &arg) 56. { 57. nElements += (nElements == 0 ? 2 : 1); 58. ArrayResize(List, nElements); 59. List[nElements - 1] = arg; 60. 61. return true; 62. 63. } 64. //+----------------+ 65. T SearchKey(const uint arg) 66. { 67. for (uint c = 1; c < nElements; c++) 68. if (List[c].GetKey() == arg) 69. return List[c]; 70. 71. return List[0]; 72. } 73. //+----------------+ 74. }; 75. //+------------------------------------------------------------------+
代码 09
为了使代码 08 继续有效,我们现在需要将其更新为如下所示的形式。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "Tutorial\File 01.mqh" 05. //+------------------------------------------------------------------+ 06. #define PrintX(X) Print(#X, " => [", X, "]") 07. //+------------------------------------------------------------------+ 08. void OnStart(void) 09. { 10. const string Names[] = 11. { 12. "Daniel Jose", 13. "Edimarcos Alcantra", 14. "Carlos Almeida", 15. "Yara Alves" 16. }; 17. 18. st_List <st_Dev01> list; 19. 20. for (uint c = 0; c < Names.Size(); c++) 21. { 22. st_Dev01 info; 23. 24. info.SetKeyInfo(c, Names[c]); 25. list.AddList(info); 26. } 27. 28. PrintX(list.SearchKey(2).GetInfo()); 29. } 30. //+------------------------------------------------------------------+
代码 10
请注意,与原始代码 08 相比,在代码 10 中,我们仅更改了第 18 行。
很好,我们现在有了模板化的列表类型。既然这些情况正是我们最近文章中展示过的,所以没有理由恐慌。下一步稍微复杂一些。然而,由于没有简单的方法来一步步解释如何操作,我们将不得不一次性介绍所有内容。或者更确切地说,分两步进行。第一个重点将是头文件,第二个重点将是主文件。所以不要惊慌,但要尽量保持专注。
首先是头文件。这在下面的代码中有所展示。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. template <typename KEY> 05. struct st_Base 06. { 07. private : 08. KEY KeyValue; 09. public : 10. //+----------------+ 11. void SetKey(const KEY arg) 12. { 13. KeyValue = arg; 14. } 15. //+----------------+ 16. KEY GetKey(void) 17. { 18. return KeyValue; 19. } 20. //+----------------+ 21. }; 22. //+------------------------------------------------------------------+ 23. template <typename KEY> 24. struct st_Dev01 : public st_Base <KEY> 25. { 26. private : 27. string ValueInfo; 28. public : 29. //+----------------+ 30. void SetKeyInfo(KEY arg1, string arg2) 31. { 32. SetKey(arg1); 33. ValueInfo = arg2; 34. } 35. //+----------------+ 36. string GetInfo(void) 37. { 38. return ValueInfo; 39. } 40. //+----------------+ 41. }; 42. //+------------------------------------------------------------------+ 43. template <typename T, typename KEY> 44. struct st_List 45. { 46. private : 47. T List[]; 48. uint nElements; 49. public : 50. //+----------------+ 51. void Clear(void) 52. { 53. ArrayFree(List); 54. nElements = 0; 55. } 56. //+----------------+ 57. bool AddList(const T &arg) 58. { 59. nElements += (nElements == 0 ? 2 : 1); 60. ArrayResize(List, nElements); 61. List[nElements - 1] = arg; 62. 63. return true; 64. 65. } 66. //+----------------+ 67. T SearchKey(const KEY arg) 68. { 69. for (uint c = 1; c < nElements; c++) 70. if (List[c].GetKey() == arg) 71. return List[c]; 72. 73. return List[0]; 74. } 75. //+----------------+ 76. }; 77. //+------------------------------------------------------------------+
代码 11
注意代码 11 的头文件中发生了什么,并将其与本文中我们之前检查过的文件进行比较。你会发现代码 11 比之前的那些有趣得多。但没有必要恐慌。至少按照我们解释的方式来看,没有必要感到恐慌。几年前,当我学习用 C 和 C++ 做这件事时,我并没有立刻掌握,因为很难找到人能向我解释它是如何工作的。但是,亲爱的读者们,你们将在这里了解它是如何运作的。
请注意:在第 04 行,编译器将执行重载。之所以会发生这种情况,正是因为我们正在为结构化代码定义一个模板。你可能已经知道,编译器会为每种需要创建的数据类型生成一个版本。这是最简单的部分。现在到了令人困惑的部分,至少在我看来是这样。
由于 st_Dev01 结构继承自 st_Base 结构(一个模板),因此 st_Dev01 结构也必须定义为模板结构。因此,无论我们如何看待这个问题:一个数据结构继承自模板数据结构的事实,迫使继承它的结构也必须是一个模板。这并不复杂,因为我们需要做的就是定义第 23 行。
然而,最困难的地方在于,我们需要将第 23 行定义的这种类型移至基结构中。换句话说,st_Base 需要接收这个值,该值将在代码的后面部分定义。这看起来很简单,但相信我,我花了很长时间才明白,我需要将代码 09 的第 22 行声明更改为代码 11 的第 24 行声明。
你无法想象在我领悟到这一点之前,我经历了多少痛苦,因为当时没有人向我解释过。我猜你总是想象着,首先需要定义第 23 行所展示的内容,然后再定义第 30 行所展示的内容。但是,如果第 24 行的定义没有修复,代码将根本无法编译。
由于我们的列表将使用一种数据结构,而这种数据结构会根据具体情况进行重载,因此我们还需要修改 st_List 结构的声明代码。但这个问题很容易解决,只需更改第 43 行,然后调整第 57 行即可。好的。我们的头文件快完成了。唯一仍然将其与特定数据类型相关联的是第 27 行的声明,但我们稍后会更改这一点。现在让我们来看看主文件变成了什么样子。它如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "Tutorial\File 01.mqh" 05. //+------------------------------------------------------------------+ 06. #define PrintX(X) Print(#X, " => [", X, "]") 07. //+------------------------------------------------------------------+ 08. void OnStart(void) 09. { 10. #define def_TypeKey uint 11. 12. const string Names[] = 13. { 14. "Daniel Jose", 15. "Edimarcos Alcantra", 16. "Carlos Almeida", 17. "Yara Alves" 18. }; 19. 20. st_List <st_Dev01 <def_TypeKey>, def_TypeKey> list; 21. 22. for (uint c = 0; c < Names.Size(); c++) 23. { 24. st_Dev01 <def_TypeKey> info; 25. 26. info.SetKeyInfo(c, Names[c]); 27. list.AddList(info); 28. } 29. 30. PrintX(list.SearchKey(2).GetInfo()); 31. 32. #undef def_TypeKey 33. } 34. //+------------------------------------------------------------------+
代码 12
“哦,上帝!请怜悯我们!这家伙疯了。他怎么敢来这里给我们看上面的代码?你们难道不关心我们这些编程新手吗?你是不是想通过展示这些疯狂的代码来害死我们?
亲爱的读者们,请冷静。别激动。代码 12 非常简单,以至于看起来几乎有点无聊,尽管它其实很有趣。但请注意。由于我们需要在代码的不同位置重复执行某些操作,因此我们在第 10 行使用了一个指令来创建一个定义,以便我们能够快速且安全地在所有这些位置进行更改。在代码 12 中,我们只需要更改两处内容。第一处是第 20 行。将此变量的声明与代码 10 中相同变量的声明进行比较。
由于重载也会影响我们正在创建的数据结构,因此我们需要更改第 24 行的声明,该行声明了将存储在列表中的结构。代码中的其他所有内容均保持不变。事实上,结果将与图 05 所示相同。
然而,我们还没有结束对这个话题的讨论,因为我们还有机会再做一次改进,现在我们就来做。这将使我们能够创建一个近乎完美的结构列表。我说“几乎”,是因为有一个小问题如果不使用类,也就是不使用 OOP 就无法解决。但稍后当我们展示问题所在以及 OOP 如何解决它时,这一点就会变得清晰。
总之,深呼吸,因为我们将更深入地探讨这个话题。回到头文件代码,我们将再次对其进行修改,如下所示。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. template <typename KEY> 05. struct st_Base 06. { 07. private : 08. KEY KeyValue; 09. public : 10. //+----------------+ 11. void SetKey(const KEY arg) 12. { 13. KeyValue = arg; 14. } 15. //+----------------+ 16. KEY GetKey(void) 17. { 18. return KeyValue; 19. } 20. //+----------------+ 21. }; 22. //+------------------------------------------------------------------+ 23. template <typename KEY, typename INFO> 24. struct st_Dev01 : public st_Base <KEY> 25. { 26. private : 27. INFO ValueInfo; 28. public : 29. //+----------------+ 30. void SetKeyInfo(KEY arg1, INFO arg2) 31. { 32. SetKey(arg1); 33. ValueInfo = arg2; 34. } 35. //+----------------+ 36. INFO GetInfo(void) 37. { 38. return ValueInfo; 39. } 40. //+----------------+ 41. }; 42. //+------------------------------------------------------------------+ 43. template <typename T, typename KEY> 44. struct st_List 45. { 46. private : 47. T List[]; 48. uint nElements; 49. public : 50. //+----------------+ 51. void Clear(void) 52. { 53. ArrayFree(List); 54. nElements = 0; 55. } 56. //+----------------+ 57. bool AddList(const T &arg) 58. { 59. nElements += (nElements == 0 ? 2 : 1); 60. ArrayResize(List, nElements); 61. List[nElements - 1] = arg; 62. 63. return true; 64. 65. } 66. //+----------------+ 67. T SearchKey(const KEY arg) 68. { 69. for (uint c = 1; c < nElements; c++) 70. if (List[c].GetKey() == arg) 71. return List[c]; 72. 73. return List[0]; 74. } 75. //+----------------+ 76. }; 77. //+------------------------------------------------------------------+
代码 13
如您所见,我在代码 13 中唯一更改的是第 23 行,仅此而已。现在,我们可以使用任何数据类型作为键,也可以使用任何数据类型作为与该键相关联的信息。理解了这一点后,我们现在就可以看看主代码是什么样子了。就在下面展示。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "Tutorial\File 01.mqh" 05. //+------------------------------------------------------------------+ 06. #define PrintX(X) Print(#X, " => [", X, "]") 07. //+------------------------------------------------------------------+ 08. void OnStart(void) 09. { 10. #define def_TypeKey string 11. #define def_TypeInfo string 12. 13. const string Names[][2] = 14. { 15. "Daniel Jose" , "Chief Programmer", 16. "Edimarcos Alcantra", "Programmer", 17. "Carlos Almeida" , "Junior Programmer", 18. "Yara Alves" , "Accounting" 19. }; 20. 21. st_List <st_Dev01 <def_TypeKey, def_TypeInfo>, def_TypeKey> list; 22. 23. for (int c = 0; c < ArrayRange(Names, 0); c++) 24. { 25. st_Dev01 <def_TypeKey, def_TypeInfo> info; 26. 27. info.SetKeyInfo(Names[c][1], Names[c][0]); 28. list.AddList(info); 29. } 30. 31. PrintX(list.SearchKey("Chief Programmer").GetInfo()); 32. PrintX(list.SearchKey("Guest").GetInfo()); 33. 34. #undef def_TypeKey 35. #undef def_TypeInfo 36. } 37. //+------------------------------------------------------------------+
代码 14
请注意,代码几乎没有变化。然而,仅凭这几个差异,就足以编写出一个非常简单的代码,无论从实用角度还是从功能角度来看,都能获得有趣的结果。由于改动非常简单,我们将给你机会去理解代码是如何工作的。无论如何,我们都会把结果展示给你看。这可以在下图中看到。

图 06
真漂亮!图 06 真是一个绝妙的例子,它展示了如果你认真学习文章中呈现的材料,看似复杂的事情可以变得多么简单。
最后的想法
毫无疑问,我可以肯定,这篇文章需要你花些时间来消化其中的内容。这是因为这里展示的所有内容最初都是 OOP 的。许多人认为,不太可能创造出一种标榜为编程类型但实际上基于完全结构化编程原则的东西。
正如我们之前所说,在解释 OOP 之前,我们需要先了解这种编程模式的最初基础是什么,因为它正是为了解决结构化编程中的难题而诞生的。然而,许多被视为 OOP 专属特权的功能,实际上并非在面向对象编程中执行,而是在结构化编程中执行。
所以,亲爱的读者们,请尝试研究这些文章中介绍的每一项内容。使用附录中的代码作为学习、练习和正确理解如何正确编程的手段。下一篇文章,精彩内容继续。回头见!
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/15922
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
神经网络在交易中的应用:用于多元时间序列预测的 LSTM 优化(终篇)
新手在交易中的10个基本错误
海豚回波定位算法(DEA)