从基础到中级:结构(二)
在上一篇文章,从基础到中级:结构(一)中,我们开始讨论一个我认为非常重要的话题,那就是要尽可能清楚地理解它。这是因为它允许我们以更简单的方式做很多事情。然而,理解每一个细节的真正重要性在于,结构介于面向对象编程和传统编程之间。
由于这个话题才刚刚开始,我们还有很多东西要讲,才能真正说:是的,我已经知道如何处理结构了。
在函数和过程中使用结构
很多初学者在处理结构时都会感到困惑,其中一个问题就是是否应该使用结构传递值。事实上,这是一个相当有趣的问题,有时会导致比其他任何问题都更大的混乱。与一些人可能认为的相反,原因正是因为我们可以通过引用传递变量,无论是传递到函数还是传递到过程。这样做的时候,我们在此类场景中使用结构时需要小心。
作为老派程序员,我经历过 C 语言不允许通过结构进行数据传输的时代,至少当时是不允许的。今天,这是可能的,但有一段时间,我们不得不使用其他机制来执行此类转移。在这种情况下,随着结构中变量的增加,出错的可能性也随之增加。但那都已经是过去的事了。如今,我们有更安全的机制来执行相同类型的转移。尽管如此,没有什么能阻止你使用更老的实现技术,其中处理速度而非安全性是主要关注点。
尽管如此,虽然这样做是可能的,但我不会演示如何实现它们,因为难度和出错的风险非常高。因此,让我们学习如何以正确的方式做事。首先,你必须明白,结构并不总是需要在不同的例程之间传递。换句话说,函数或过程不一定需要知道它正在处理数据结构或处理离散值。
当我们使用其他文章中已经展示和解释过的机制时,我们可以让代码更加灵活、更加可用。因为一旦我们开始使用结构,我们可能最终会限制某个特定程序的某些活动。
为了理解这一点,让我们看看下面显示的一段简单代码。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. MqlDateTime dt; 07. 08. TimeToStruct(TimeLocal(), dt); 09. 10. Print(DateToString(dt.day, dt.mon, dt.year)); 11. Print(DateToString(3, 15, 2024)); 12. } 13. //+------------------------------------------------------------------+ 14. string DateToString(int day, int month, int year) 15. { 16. if ((day < 1) || (day > 31) || (month < 1) || (month > 12)) 17. return "ERROR..."; 18. 19. return StringFormat("%02d/%02d/%d", day, month, year); 20. } 21. //+------------------------------------------------------------------+
代码 01
执行此代码 01 时,将产生如下所示的结果:

图 01
请注意,在代码 01 中,我们几乎没有使用任何复杂或难以理解的内容。但运行这段代码时看到的结果肯定与图 01 所示不同。原因很简单:使用了 TimeLocal 函数。此函数来自标准 MQL5 库,用于获取当前时钟值并以 datetime 格式返回。这种格式为 8 字节类型,包含 MetaTrader 5 运行所在地点的当前日期和时间信息。当然,这个时间是以秒为单位计算的,但这与我们的目的无关。但是,TimeLocal 返回的这个值被 TimeToStruct 函数用来填充 MqlDateTime 结构。这就是我们想要到达的地方。MqlDateTime 结构的声明方式如下所示:
struct MqlDateTime { int year; int mon; int day; int hour; int min; int sec; int day_of_week; int day_of_year; };
MqlDateTime 声明
现在请注意我接下来要解释的内容,因为这对于理解我们在代码 01 中所做的工作非常重要。TimeToStruct 函数将我们所知的 64 位 datetime 类型值分解为值,并为上面结构声明中的每个变量分配适当的值。还有其他方法可以做到这一点,但这并不是重点。关键在于,赋值之后,我们就可以像代码 01 的第 10 行所示那样使用它们。然而,请注意,这只是因为在第 14 行的函数中,我们使用原语类型参数来接收数据。这样我们就可以舍弃不必要的值,只使用我们实际需要的部分。
然而,同样也允许像第 11 行那样使用。但是由于第 16 行的检查,第 14 行的例程最终返回错误,因为我们不可能有一年有 15 个月。
这正是我想要强调的情况。代码 01 中所示的相同方法可以用各种不同的方式实现,例如下面的代码 02 中所示的方式。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. MqlDateTime dt; 07. 08. TimeToStruct(TimeLocal(), dt); 09. 10. Print(DateToString(dt)); 11. dt.day = 3; 12. dt.mon = 15; 13. dt.year = 2024; 14. Print(DateToString(dt)); 15. } 16. //+------------------------------------------------------------------+ 17. string DateToString(MqlDateTime &arg) 18. { 19. if ((arg.day < 1) || (arg.day > 31) || (arg.mon < 1) || (arg.mon > 12)) 20. return "ERROR..."; 21. 22. return StringFormat("%02d/%02d/%d", arg.day, arg.mon, arg.year); 23. } 24. //+------------------------------------------------------------------+
代码 02
在这种情况下,我们将得到与图 01 中相同的结果。但是,要理解错误的原因就容易得多,因为只需查看代码 02 中的第 12 行,我们就能清楚地看到哪里出了问题。但请注意,在第 17 行,需要通过引用传递结构。这可能会造成问题,但也很容易解决,因为我们只需要确保接收到的参数被视为常量即可。如何做到这一点已经在其他文章中进行了解释。尽管如此,事实仍然是,该函数现在拥有比以前多得多的特权,因为它可以看到比其运行所需的更多数据。因此,对于如何在函数或过程中使用结构,并没有一个普遍适用的正确答案,因为您可能会传递比必要信息多得多的信息。
在讨论类和对象时,一个函数或过程应该具备的关于其他数据的最小知识这一概念更常被提及。这是因为在面向对象编程中,这个原则比你想象的要现实得多。但这又是另一个话题了。就目前而言,你必须明白,一个例行程序(无论是函数还是程序)需要知道的越少越好。这减少了我们需要声明和传递的信息量,加快了实现以及未来的调试和改进。
好吧,但我们刚才看到的这两段代码与某个特定活动有关。然而,即使 MQL5 已经提供了 12 个预定义的结构,但实际上创建自定义结构的情况也并不少见。将来我们会详细介绍这些结构,不过,我们已经简要讨论过其中一个结构:MqlDateTime。但这只是简单介绍一下我们以后能做什么,会做什么。
亲爱的读者,您现在或许会想:如何实现一个使用我自己创建的结构的功能呢?好吧,为了恰当地回答这个问题,我们会创造一个新的话题,因为我不想混搭主题。尽管答案与当前主题有些关联。不过,我还是想让一切整洁。
使用自定义结构
虽然上一主题结尾提出的问题与此密切相关,但为了使材料尽可能具有教学意义,我想将内容分开讲解。这是因为当我们使用 MQL5 中的 12 个预定义结构之一时,我们可以以某种方式实现某些功能。但是,当我们使用自己创建的结构来实现某些功能时,我们通常需要以不同的方式来实现这些事情。这是由于前一篇文章中解释的原因造成的。我们稍后会讨论另一个原因。
但在此之前,让我们先了解一些事情:
结构是一种特殊的数据类型,可以包含任何信息。
正确理解这句话非常重要。当你真正理解了结构究竟是什么时,就更容易理解如何在代码中实现和使用它。所以,亲爱的读者,请理解结构与 uchar、double 或任何其他类型并无不同。正如这些类型允许我们存储和处理其中包含的值一样,我们也可以使用结构来做同样的事情。这与我们之前看到的联合有些类似,联合实际上并不是一种特殊的数据类型,而是一组共享公共内存区域的数据。
为了强化结构确实是一种特殊数据类型的概念,让我们考虑一下注册记录。在注册记录中,我们可能拥有各种信息,例如名字、姓氏、地址、职业、联系方式等等。所有这些数据都可以进行整理,使它们共享相同的基础格式。这使得以非常简单实用的方式处理记录成为可能。
基本上有两种方法可以做到这一点。第一种是,我们创建一系列变量来存储我们想要存储在记录中的信息。这些变量分散在代码的各个角落,类似下图所示。当然,这只是一个假设的例子。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. string Name, 07. Surname, 08. Address, 09. Position; 10. double Remuneration; 11. datetime Birth, 12. Hiring; 13. . . . Registration routines . . .
代码 03
除非您正在创建一个应用程序,其目的是处理一条记录,否则任何真正的代码都不太可能像代码 03 中显示的那样。不过,如果记录存储在磁盘上,可能确实有类似代码 03 的代码。但这就是一些问题出现的地方,例如,变量需要存储的顺序。然而,这个问题目前并不是我们真正感兴趣的。真正的问题是处理不同记录中数据之间的交叉引用或关系。
好吧,那你怎么处理呢?最常见的方法是创建数组。既然我们已经谈到了数组,你可能马上就会想到创建如下所示的东西:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. #define def_Number_of_Records 10 07. 08. string Name[def_Number_of_Records], 09. Surname[def_Number_of_Records], 10. Address[def_Number_of_Records], 11. Position[def_Number_of_Records]; 12. double Remuneration[def_Number_of_Records]; 13. datetime Birth[def_Number_of_Records], 14. Hiring[def_Number_of_Records]; 15. . . . Registration routines . . .
代码 04
请注意,现在我们可以同时处理多条记录。不过,这种技术虽然有效,但相当笨拙。这是因为,如果我们需要添加或删除某个字段,我们将面临大量的工作来进行简单的更改。我指的是实现方式,而不是用法。但当看到代码 04,并考虑到我们到目前为止对结构的讨论时,你可能会想:我们能不能把代码 04 中的所有内容都放到一个结构里?这正是事情开始变得有意义的时候。因为在早期代码中看到的所有复杂性都与我们接下来看到的类似。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. #define def_Number_of_Records 10 07. 08. struct st_Register 09. { 10. string Name, 11. Surname, 12. Address, 13. Position; 14. double Remuneration; 15. datetime Birth, 16. Hiring; 17. }; 18. 19. st_Register Record[def_Number_of_Records]; 20. . . . Registration routines . . .
代码 05
注意这个想法是多么自然地出现。我们不需要创建一个完整的应用程序,就可以让这个概念成形并有意义。但是现在 —— 在真正有趣的地方 —— 代码 05 第 8 行声明的结构将只能在 OnStart 过程中访问,或者在声明它的任何地方访问。在这种情况下,您必须明白,st_Register 结构不能像代码 02 中那样用于将数据传递给函数或过程。您需要采用与代码 01 非常相似的方法,以利用代码 05 第 8 行声明的结构中的任何信息。
在某些情况下,这会造成问题。而在其它情况下,这可能正是我们需要的。但一般来说,结构(与联合不同)是以全局作用域声明的。这使我们能够以更愉快、更便捷的方式传输数据。
任何对 Python 或 JavaScript 等语言稍有了解的人都知道,使用类似下面所示的代码非常常见:
cmd_entry.pack(fill='x', side='left' , expand=1)
这行代码在 Python 和 JavaScript 中都有效,但对于使用 MQL5、C 或 C++ 的人来说,它却毫无意义。这是因为,在这些语言中,当你看到一个值被分配给一个变量时,你实际上是在给代码中存在的一个变量赋值,无论是全局变量还是局部变量。如果你搜索该变量,你会发现它位于以 MQL5、C 或 C++ 语句表示的作用域之一中。
但是,如果同样的代码片段出现在 Python 或 JavaScript 中,我们实际上并没有给变量赋值。我知道这听起来完全不合逻辑,但我们是在给类似结构的东西赋值。因此,我们可以按任意顺序为任何元素赋值,这在 MQL5 中是不可能的。
然而,如果我们用 Python 或 JavaScript 来查看同样的代码,将其视为类似于 MQL5 中的结构,一切就都变得有意义了。它将使函数和过程之间传递值变得更简单、更愉快,尤其是当需要传递许多参数时。
现在请密切关注,因为这可能会影响你处理未来问题的方式。在之前的一些文章中,我们讨论了这样一个事实:我们可以重载函数和过程。这使我们能够处理不同类型或不同数量的参数。但是,如果在某些情况下,我们使用结构来传递这些值,那么许多原本必要的重载就不再需要了。这是因为我们只需将参数放置在一个结构中,就可以传递任意数量的参数。
很有趣吧?的确,没有完美、绝对的做事方式。当然,有些方法或多或少都很有趣,这取决于每个具体案例。
因此,让我们假设如下:假设代码 05 实际上使用了几个函数和过程,每个函数和过程都需要更多或更少的参数。可以使用代码 01 中的方法控制每个参数,但代价是可能需要在多个地方进行函数重载。你也可以采用代码 02 中看到的方法,但代价是一些值可能会在你没有注意到的情况下被修改。然而,有一种解决方案在很多情况下确实被采纳了。该解决方案是指,当需要修改某些内容时使用函数,而当不需要修改,只需要参考时使用过程。
无论如何,代码 05 第 8 行声明的结构都需要移出局部范围,置于全局范围,如下面的假设代码所示。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. struct st_Register 05. { 06. string Name, 07. Surname, 08. Address, 09. Position; 10. double Remuneration; 11. datetime Birth, 12. Hiring; 13. }; 14. //+------------------------------------------------------------------+ 15. void OnStart(void) 16. { 17. st_Register Record[10]; 18. uchar Counter = 0, 19. Index; 20. 21. Index = Counter; 22. Counter += (NewRecord(Record[Counter], "Daniel", "Jose", "Brasil", "1971/03/30") ? 1 : 0); 23. Record[Index] = UpdatePosition(Record[Index], "Chief Programmer"); 24. Index = Counter; 25. Counter += (NewRecord(Record[Counter], "Edimarcos", "Alcantra", "Brasil", "1974/12/07") ? 1 : 0); 26. Record[Index] = UpdatePosition(Record[Index], "Programmer"); 27. Index = Counter; 28. Counter += (NewRecord(Record[Counter], "Carlos", "Almeida", "Brasil", "1985/11/15") ? 1 : 0); 29. Record[Index] = UpdatePosition(Record[Index], "Junior Programmer"); 30. Index = Counter; 31. Counter += (NewRecord(Record[Counter], "Yara", "Alves", "Brasil", "1978/07/25") ? 1 : 0); 32. Record[Index] = UpdatePosition(Record[Index], "Accounting"); 33. 34. Print("Number of records: ", Counter); 35. ViewRecord(Record[3]); 36. } 37. //+------------------------------------------------------------------+ 38. bool NewRecord(st_Register &arg, const string Name, const string Surname, const string Address, const string Birth) 39. { 40. arg.Name = Name; 41. arg.Surname = Surname; 42. arg.Address = Address; 43. arg.Birth = StringToTime(Birth); 44. arg.Hiring = TimeLocal(); 45. 46. return true; 47. } 48. //+------------------------------------------------------------------+ 49. st_Register UpdatePosition(const st_Register &arg, const string NewPosition) 50. { 51. st_Register info = arg; 52. 53. info.Position = NewPosition; 54. 55. return info; 56. } 57. //+------------------------------------------------------------------+ 58. void ViewRecord(const st_Register &arg) 59. { 60. PrintFormat("Collaborator: %s, %s\nPosition: %s\nBirth in: %s", 61. arg.Surname, 62. arg.Name, 63. arg.Position, 64. TimeToString(arg.Birth, TIME_DATE)); 65. } 66. //+------------------------------------------------------------------+
代码 06
我知道代码 06 在某种程度上是主观的 —— 很多人可能认为不太可能创建出这样的代码。但尽管这只是一个假设的例子,但它包含许多出现在现实世界代码中的元素和问题。执行后,会产生以下结果:

图 03
我们这里发生了几件事。虽然代码对一些人来说可能看起来复杂、难以理解,但对于那些一直在学习和实践文章中展示的内容的人来说,这段代码其实相当简单、清晰、直白。但是,由于有些元素可能看起来很奇怪,比如出生日期,我将快速解释正在发生的事情。
在第 4 行,我们声明数据结构。请注意,它是在全局作用域中声明的。因此,它可以在主代码块之外使用 —— 在本例中,即在 OnStart 过程中使用。在主代码块中,我们在第 17 行进行了声明,目的是创建一个信息列表。换句话说,我们的结构确实会被视为一种特殊类型的变量。在此之后,在第 21 行和第 32 行之间,我们在列表中添加了几条记录。请记住,数组中的每个值都像一个完全独立的元素。然而,由于我们将所有内容都组织在一个块中(第 4 行声明的结构),感觉就像每个元素都与其他元素物理连接在一起,形成了一种记录表。
因此,在第 21 行到第 32 行之间的每次调用中,我们首先尝试通过第 38 行的函数向数组中添加一条新记录。请注意,我们在第 38 行授予该函数必要的最低权限。我们只提供一些信息,而其他值可以由例程本身直接分配,比如在第 44 行,我们取当前日期并使用它设置招聘日期。实际上,事情往往就是这样做的,因为创建员工记录的那一刻,实际上就是他们被雇佣的那一刻。因此,我们允许第 38 行的函数为返回的结构赋值 —— 该值不是作为参数传递的。
当然,在真实代码中,在注册阶段会有很多检查来防止出错。但由于这段代码纯粹是教学性质的,因此没有必要进行此类检查。
由于每个记录都需要其他数据,这些数据不会直接作为参数传递给第 38 行的 NewRecord 函数,因此我们使用第49行来更新或为已经创建的记录分配一些特定的值。同样,在真实代码中,这里会有很多检查。
但第 49 行函数的目的是展示如何返回一个值,在这种情况下,就是函数内部创建的结构。这种做法让我们能够更好地控制何时何地更改结构中的值。
请注意,与第 38 行不同的是,第 49 行修改了输入结构,并将其返回给调用者。由调用者决定在何处以及如何使用返回值。是的,亲爱的读者,你可以通过多种方式使用这个返回值,后面会显示。
最后,我们有一个显示记录的小程序。它给我们带来了如图 03 所示的结果。
我们能否创建相同的代码 06,但以不同的方式完成工作?是的,而这正是我们将在下面的代码中看到的:
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. struct st_Register 005. { 006. uchar ID; 007. string Name, 008. Surname, 009. Address, 010. Position; 011. double Remuneration; 012. datetime Birth, 013. Hiring; 014. }gl_Record[10]; 015. //+------------------------------------------------------------------+ 016. void OnStart(void) 017. { 018. uchar Index; 019. 020. ZeroMemory(gl_Record); 021. 022. NewCollaborator("Daniel", "Jose", "Brasil", "1971/03/30", "Chief Programmer"); 023. NewCollaborator("Edimarcos", "Alcantra", "Brasil", "1974/12/07", "Programmer"); 024. NewCollaborator("Carlos", "Almeida", "Brasil", "1985/11/15", "Junior Programmer"); 025. 026. Index = GetNumberRecord(); 027. 028. if (NewRecord(gl_Record[Index], "Yara", "Alves", "Brasil", "1978/07/25")) 029. { 030. gl_Record[Index].ID = Index + 1; 031. gl_Record[Index] = UpdatePosition(gl_Record[Index], "Accounting"); 032. } 033. 034. Print("Number of records: ", GetNumberRecord()); 035. Print("--------------------"); 036. 037. for(uchar c = 1; ViewRecord(c); c++) 038. Print("********************"); 039. } 040. //+------------------------------------------------------------------+ 041. bool NewCollaborator(const string Name, const string Surname, const string Address, const string Birth, const string Position) 042. { 043. st_Register info; 044. uchar Index = 0; 045. 046. if (!NewRecord(info, Name, Surname, Address, Birth)) 047. return false; 048. 049. info = UpdatePosition(info, Position); 050. 051. while (gl_Record[Index].ID) 052. { 053. if (Index >= gl_Record.Size()) return false; 054. Index++; 055. } 056. 057. info.ID = Index + 1; 058. gl_Record[Index] = info; 059. 060. return true; 061. } 062. //+------------------------------------------------------------------+ 063. bool NewRecord(st_Register &arg, const string Name, const string Surname, const string Address, const string Birth) 064. { 065. arg.Name = Name; 066. arg.Surname = Surname; 067. arg.Address = Address; 068. arg.Birth = StringToTime(Birth); 069. arg.Hiring = TimeLocal(); 070. 071. return true; 072. } 073. //+------------------------------------------------------------------+ 074. st_Register UpdatePosition(const st_Register &arg, const string NewPosition) 075. { 076. st_Register info = arg; 077. 078. info.Position = NewPosition; 079. 080. return info; 081. } 082. //+------------------------------------------------------------------+ 083. uchar GetNumberRecord(void) 084. { 085. uchar counter = 0; 086. 087. for (uchar c = 0; c < gl_Record.Size(); counter += (gl_Record[c].ID ? 1 : 0), c++); 088. 089. return counter; 090. } 091. //+------------------------------------------------------------------+ 092. bool ViewRecord(const uchar ID) 093. { 094. st_Register info; 095. 096. ZeroMemory(info); 097. 098. for (uchar c = 0; (c < gl_Record.Size()) && (!info.ID); c++) 099. info = (gl_Record[c].ID == ID ? gl_Record[c] : info); 100. 101. if (info.ID) 102. PrintFormat("Collaborator: %s, %s\nPosition: %s\nBirth in: %s", 103. info.Surname, 104. info.Name, 105. info.Position, 106. TimeToString(info.Birth, TIME_DATE)); 107. else 108. Print("Record ID [", ID ,"] not found."); 109. 110. return (bool) info.ID; 111. } 112. //+------------------------------------------------------------------+
代码 07
好吧,现在我们手里真的有一件相当复杂的东西。那是因为代码 07 肯定更复杂,我对这里正在做的事情知之甚少。(哈哈)其实,从本质上讲,代码 07 非常简单。坦白地说,在我看来,代码 07 比代码 06 更简单,尽管它们本质上做的是同一件事。但是,我理解有些读者可能会觉得代码 07 更复杂。
首先,我们现在在第 14 行声明了一个全局变量。这使得任何事情看起来都比自然需要的更加复杂。但在我们详细讨论代码07正在做什么之前,让我们先看看执行结果,如下图所示:

图 04
请注意,我们在主块中创建的所有记录都出现在这张图片中,这是完美的,因为它表明代码 07 正在履行其职责。但这是如何实现的,尤其是考虑到这里出现的新元素?好吧,亲爱的读者,正如前一篇文章所提到的,结构代表着传统编程和面向对象编程之间的一步。然而,我们还很早就在探索如何真正利用结构。
请注意,第 22 行到第 32 行用于向系统中插入新记录。这可能是关键点:第 22 行和第 24 行之间创建记录的方式乍看起来相对简单,不同于第 26行 和第 32 行之间发生的情况。在那里,我们正在执行与第 41 行上的函数相同的工作,但完全是手动的。仅仅是这样做的事实就让代码有点风险。即使是一个小小的失误,也可能需要数小时的故障排除。
我建议你仔细学习代码 07,以便更好地理解其实际应用。比较使用代码 06 生成结果的难度,以及使用代码 07 实现相同结果的难度。理解这一点将使我们更容易理解下一篇文章将讨论的内容。
然而,代码 07 中最复杂的部分可能是第 92 行的函数。让我们来分析一下它是如何运作的。首先,在第 94 行,我们声明一个临时结构。然后,在第 96 行,我们清除将要放置该结构的内存区域。这允许我们使用第 98 行的循环测试来确定何时找到请求的 ID。
循环随后结束,这样在第 101 行,我们就可以检查 MetaTrader 5 终端中是否有内容要打印。如果找到了 ID,我们就打印记录结构的内容。否则,我们将打印第 108 行所示的消息。
由于此函数的返回类型必须是布尔值,而 ID 是数值,因此我们告知编译器我们知道这一事实。因此,在第 110 行,我们执行显式类型转换,以返回调用方对请求的 ID 的搜索是否成功。这个返回很重要,这样第 37 行的循环就知道何时停止。
最后的探讨
在本文中,我们看到了如何在相对简单的代码中使用结构。虽然这里的一切主要都是教学性的,但我们还是构建了一个与数据库非常相似的系统。真正的数据库确实需要进行大量的测试来防止错误。但我认为主要目标已经实现:展示结构的概念。
换句话说,结构确实是一种特殊类型的变量。所以,请花时间学习和练习附件中提供的代码。虽然没有逐行解释每段代码,但我认为这并不是严格必要的,因为这里的内容对于任何一直在学习和实践文章中介绍的材料的人来说都是可以理解的。
在下一篇文章中,我们将探索如何以更实用、更有趣的方式使用结构,使编码过程显著简化。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/15731
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
混沌博弈优化(CGO)
新手在交易中的10个基本错误
从基础到中级:结构(一)