
从基础到中级:按值传递还是按引用传递
概述
此处提供的内容仅用于教育目的。在任何情况下,除了学习和掌握所提出的概念外,都不应出于任何目的使用此应用程序。
在上一篇文章“从基础到中级:运算符”中,我们探索了算术和逻辑运算。虽然解释有点肤浅,但足以让我们转向其他话题。随着时间的推移和更多文章的发表,我们将逐步深入研究最初介绍的主题。
因此,要有耐心,花时间学习材料。成果不会一夜之间出现;而是需要奉献和坚持。通过现在开始学习,你几乎不会注意到随着时间的推移复杂性的增加。正如前一篇文章所述,我们即将开始讨论控制运算符。然而,在我们深入探讨这个话题之前,我们需要解决另一个重要的概念。这将使学习控制操作符更具吸引力和乐趣,因为您将更好地了解使用它们可以实现什么。
为了充分和正确地理解本文将要解释和演示的内容,需要一个先决条件:理解变量和常量之间的区别。如果您不熟悉这种区别,请参阅文章从基础到中级:变量(一) 。
初学者编写的程序中最常见的混淆和错误来源之一是知道何时在函数和过程中使用值传递以及何时使用引用传递。根据具体情况,通过引用可能更实用。然而,按值传递通常更安全。但你什么时候应该选择一个而不是另一个呢?嗯,亲爱的读者,这得看情况。对于这种做法,没有绝对或明确的规则。在某些情况下,通过引用传递确实是最佳选择,而在其他情况下,按值传递是适当的方法。
通常,编译器会尝试做出选择,以产生尽可能高效的可执行代码。然而,了解每个场景的需求至关重要,这样你才能编写安全高效的代码。
按值传递
为了在实践中开始理解这个概念,最合适的方法是使用易于实现的代码。因此,让我们从检查第一个使用示例开始。这将在下面的代码中按值传递。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. int info = 10; 07. 08. Print(__FUNCTION__, " : #1 => ", info); 09. Procedure(info); 10. Print(__FUNCTION__, " : #2 => ", info); 11. } 12. //+------------------------------------------------------------------+ 13. void Procedure(int arg1) 14. { 15. Print(__FUNCTION__, " : #1 => ", arg1); 16. arg1 = arg1 + 3; 17. Print(__FUNCTION__, " : #2 => ", arg1); 18. } 19. //+------------------------------------------------------------------+
代码 01
当执行代码 01 时,您将在 MetaTrader 5 终端中看到类似于下图 01 所示的内容。
图 01
我知道,对于很多人来说,图 01 中所示的内容可能看起来相当复杂。但是,既然您致力于学习如何在 MQL5 中正确编程,让我们花点时间了解一下这张图片告诉我们什么。为此,您需要密切关注代码 01 和图 01 中显示的内容。
根据前几篇文章中的解释,您应该已经知道,在第六行,我们正在定义一个具有给定值的变量。在第八行,我们打印同一个变量。但是,如果您查看图像,您会注意到还有其他信息正在打印。在这个例子中,它是第八行所在的过程或函数的名称。
现在,亲爱的读者,请密切注意。在代码 01 的编译阶段,编译器一旦遇到预定义的宏 __FUNCTION__,就会搜索当前例程的名称。在本例中,该名称在第四行上定义,即 OnStart。一旦找到该名称,编译器就会用 OnStart 替换 __FUNCTION__,从而创建一个新的字符串打印到终端。这是因为 Print 函数将其输出定向到标准输出,在 MetaTrader 5 中,标准输出是终端,如图 01 所示。因此,我们得到了例程的名称和第六行声明的变量的值。还有其他预定义的宏,每个宏在特定情况下都非常有用。一定要研究它们,因为它们非常有助于跟踪你的代码在做什么。就像 OnStart 例程中的 __FUNCTION__ 被其名称替换一样,如果在 Procedure 例程中使用,它也将被替换。这解释了所示数值之前的信息。
现在,让我们重新理解按值传递。因为我们使用的是按值传递系统,所以当执行第九行的函数调用时,第六行定义的变量所持有的值将传递给第十三行声明的过程。这里有一个重要的观察。尽管我说我们正在将“info”变量的值“传递”或“复制”到“arg1”中,但这并不总是真正发生的。通常,编译器会以非常有效的方式使“arg1”指向“info”。然而(事情从这里开始变得真正有趣)尽管“arg1”指向“info”,但它并不共享相同的内存空间。发生的情况是,编译器使 “arg1” 引用 “info”,使其能够“查看”和“使用”其值,但不对其进行修改 — 就像透过玻璃窗看一样。你可以看到另一边是什么,但你不能碰它。同样,“arg1”将“info”视为常量。
因此,当我们在第十五行打印 “arg1” 值时,我们在图 01 中看到第二行显示 “arg1” 和 “info” 的相同值。然而,当我们在第十六行更改“arg1”值时,会发生一些重要的事情:在编译过程中,当知道将发生此操作时,编译器会分配新的空间来存储与 “info” 相同数量的字节。即便如此,“arg1” 最初仍会“观察” “info”。但是当执行第十六行时,“arg1” 将采用 “info” 的当前值,就好像它是不可变的一样,并为自己创建一个本地副本。从那一刻起,“arg1” 就完全独立于 “info”。因此,当执行到第十七行时,我们得到了图 01 中打印出的第三行,显示 “arg1” 的值确实发生了改变。
然而,当程序返回并执行第十行时,会打印图 01 中的第四行,显示 “info” 值保持不变。
亲爱的读者,这基本上就是按值传递的工作原理。当然,具体的实现取决于编译器的构建方式,但这是总体思路。
很好,这是容易的部分。然而,即使是这种按值传递的机制,也可以在代码中以稍微不同的方式使用。我现在不会详细说明这件事。这是因为在讨论使用值传递的其他方法之前,我们需要更多地介绍运算符和数据类型。在这里讨论这个问题会让事情变得更复杂,而不是更清晰。那么,让我们一步一步地进行。尽管如此,即使是我们刚刚研究的这种机制也可以进行调整。这将使我们能够避免上面例子中发生的事情。为了正确地探讨这个问题,让我们考虑一个小的假设情况。假设由于某种原因,您不希望修改 “arg1” 的值。您希望它仅引用 “info”,并且每次使用 “arg1” 时,您希望它反映 “info” 的当前值。
那么,我们如何实现这一目标呢?有很多方法。一是要格外小心,不要修改过程块内的 “arg1”。虽然这看起来很简单,但实际上并非如此。通常,在没有意识到的情况下,我们最终会更改一个变量值,只有在程序执行过程中出现奇怪行为时才会注意到这个问题。这些情况可能需要花费大量时间和精力来解决。然而,有一个更好的解决方案:让编译器为我们做这项工作,如果我们尝试了不应该做的事情,它会警告我们。
为了明确这一点,让我们看看以下代码。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. int info = 10; 07. 08. Print(__FUNCTION__, " : #1 => ", info); 09. Procedure(info); 10. Print(__FUNCTION__, " : #2 => ", info); 11. } 12. //+------------------------------------------------------------------+ 13. void Procedure(const int arg1) 14. { 15. Print(__FUNCTION__, " : #1 => ", arg1); 16. arg1 = arg1 + 3; 17. Print(__FUNCTION__, " : #2 => ", arg1); 18. } 19. //+------------------------------------------------------------------+
代码 02
尝试编译代码02时,将显示以下错误消息。
图 02
显然,您可以看到编译器指示错误在第 16 行。发生这种情况是因为我们试图修改变量 “arg1” 的值。但是请稍等 — “arg1” 不再是一个常规变量,它已被声明为常量。因此,在整个 Procedure 函数中,您将无法再更改 “arg1” 的值。这有效地消除了我们之前发现的风险,因为编译器本身现在将确保 “arg1” 不能被修改。换句话说,了解自己在做什么并正确理解这些概念是非常有用的 — 它使编程过程更加高效。
这是否意味着我们不能更改将在代码 02 第 17 行打印的值?是的,亲爱的读者,您仍然可以修改将要打印的值,只要将其分配给另一个变量,即使这个新变量未在前面的代码中明确声明。因此,为了实现与图 01 所示相同的输出,同时使用类似于代码 02 的方法,我们可以编写非常接近下图所示的代码。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. int info = 10; 07. 08. Print(__FUNCTION__, " : #1 => ", info); 09. Procedure(info); 10. Print(__FUNCTION__, " : #2 => ", info); 11. } 12. //+------------------------------------------------------------------+ 13. void Procedure(const int arg1) 14. { 15. Print(__FUNCTION__, " : #1 => ", arg1); 16. Print(__FUNCTION__, " : #2 => ", arg1 + 3); 17. } 18. //+------------------------------------------------------------------+
代码 03
通过进行必要的调整来创建代码 03,我们继续将 “arg1” 视为常量。然而,在代码 03 的第 16 行,我们将两个常量(在本例中为 arg1 和值 3)的总和赋给另一个变量。这个新变量由编译器创建,以允许 Print 正确显示结果。当然,您可以专门为此目的创建一个专用变量。然而,我认为没有必要这样做,至少在这里介绍的这种代码中没有。
通过引用传递
在例程之间传递值的另一种方法是通过引用。在这种情况下,我们必须采取一些额外的预防措施。但在我们继续之前,我想稍作停顿,解释一下此时非常重要的事情。
亲爱的读者,您应该尽可能避免使用引用传递,除非这是真正绝对必要的。难以解决错误的最常见原因之一是引用传递的不当或粗心使用。事实上,对于一些程序员来说,这几乎成了一种习惯,这可能会使修复和改进现有代码成为一场真正的噩梦。因此,除非严格需要,否则应避免使用引用传递,特别是当唯一目的是修改单个值时。我稍后将展示一个例子。但在此之前,让我们首先检查一个使用引用传递的情况,并了解这对应用程序有什么影响。为了说明这一点,我们来分析下面的代码。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. int info = 10; 07. 08. Print(__FUNCTION__, " : #1 => ", info); 09. Procedure(info); 10. Print(__FUNCTION__, " : #2 => ", info); 11. } 12. //+------------------------------------------------------------------+ 13. void Procedure(int & arg1) 14. { 15. Print(__FUNCTION__, " : #1 => ", arg1); 16. arg1 = arg1 + 3; 17. Print(__FUNCTION__, " : #2 => ", arg1); 18. } 19. //+------------------------------------------------------------------+
代码 04
现在,请密切关注我在这里要解释的内容。因为这是事情变得非常棘手的地方,尤其是当你处理真正复杂的代码时。当您执行此代码时,您将在 MetaTrader 5 终端中看到以下输出:
图 03
当这种问题无意中发生时,可能会导致您浪费数小时、数天甚至数月的时间试图弄清楚为什么代码没有按预期工作。请注意,结果与图 01 所示完全不同,特别是图 03 中的第四行。但为什么会发生这种情况呢?毕竟,代码 04 看起来与代码 01 完全相同,对吧?图 03 的第四行与图 01 的不同是没有道理的。
好吧,我亲爱的读者,尽管外观不同,代码 01 和 代码 04 还是有一个细微但至关重要的区别。这个差异就在第 13 行。我特意强调了这一点,这样你就可以理解发生了什么。仔细观察一下 代码 04 中出现但在 代码 01 中不存在的一个看似无辜的符号。该符号是 & ,也称为与号。这正是图 03 和图 01 之间差异的原因。通常,在使用 MQL5 时,您会看到此符号用于按位逻辑运算,具体来说是 AND(与) 运算。然而,当谈到 C 和 C++ 时,除了将其应用于 AND 运算之外,它还用于引用内存中的变量。
现在,事情变得非常严重。因为如果很难理解一个简单的符号意味着什么,想象一下,当同一个符号在同一个代码中有两个完全不同的含义的时候,这简直太疯狂了。这就是为什么 C 和 C++ 编程如此复杂并且需要很长时间才能掌握的原因之一。但是,幸运的是,在 MQL5 中,事情稍微简单一些。至少,我们不用直接处理 C 和 C++ 中最令人困惑的机制之一:指针。
亲爱的读者,为了帮助你正确理解为什么代码 04 第 13 行上的这个符号会影响最终结果,我们需要澄清这个符号的真正含义。所以,让我解释一下 C/C++ 中的指针概念,但不要深入探讨其中涉及的所有复杂性。
指针就像一个变量,但又不是任意的一个变量。顾名思义,它指向某个东西。具体来说,指针指向存储变量的内存地址。我知道这听起来可能令人困惑 — 一个变量指向另一个变量。但是这个概念在 C 和 C++ 中被广泛用于编写高效和多样化的代码。这就是为什么 C 和 C++ 是最快的语言之一,在执行速度方面可以与汇编相媲美。但为了避免混淆,我们不要深入探讨指针。这里您需要理解的是,当像代码 04 中显示的那样声明 “arg1” 时,我们并没有使用常规的方式来创建 “info” 变量。相反,MQL5 编译器将 “arg1” 视为指向 “info” 的指针。
因此,当我们在第 16 行执行加法时,我们不会改变 “arg”。我们不会添加到 “arg1” 本身。我们实际上是在修改 “info”,因为 “arg1” 和 “info” 共享相同的内存空间。在 Procedure 例程内部,“arg1” 是 “info”,而 “info” 是 “arg1”。
困惑吗?这是可以理解的。这实际上是使用指针的“简单”和“容易”的部分。幸运的是,由于 MQL5 不允许(并且不会)让我们像在 C/C++ 中那样直接使用指针,因此我们对 “arg1” 如何修改 “info” 的解释可以到此为止。现在您应该将 “arg1” 和 “info” 视为同一个实体,即使它们是在不同的地方声明并且看起来毫无关联。在 C/C++ 中,这会更加复杂。亲爱的读者,为了避免让你不知所措,我们就到此为止。
现在有一个问题:有没有办法阻止这种修改?意思是,有没有办法防止第 16 行更改 “arg1” 时也修改 “info”?是的!正如上一主题所解释的,一种方法是使用按值传递。另一种方法是修改代码 04,使其看起来像代码 05 中显示的那样。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. int info = 10; 07. 08. Print(__FUNCTION__, " : #1 => ", info); 09. Procedure(info); 10. Print(__FUNCTION__, " : #2 => ", info); 11. } 12. //+------------------------------------------------------------------+ 13. void Procedure(const int & arg1) 14. { 15. Print(__FUNCTION__, " : #1 => ", arg1); 16. arg1 = arg1 + 3; 17. Print(__FUNCTION__, " : #2 => ", arg1); 18. } 19. //+------------------------------------------------------------------+
代码 05
然而,当我们编写代码 05 时,我们陷入了与代码 02 相同的情况。换句话说,“arg1” 将被视为常量。尝试编译代码 05 将产生与图 02 中相同的错误。即使 “arg1” 指向变量 “info”,这也不是一个编译器错误。事实上,有时我们被迫做一些像代码 05 这样的事情。但它仍然无法编译成功,因为第 16 行试图修改 “arg1”。该问题的最终解决方案是采用类似于代码 03的代码,结果是如下所示的代码:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. int info = 10; 07. 08. Print(__FUNCTION__, " : #1 => ", info); 09. Procedure(info); 10. Print(__FUNCTION__, " : #2 => ", info); 11. } 12. //+------------------------------------------------------------------+ 13. void Procedure(const int & arg1) 14. { 15. Print(__FUNCTION__, " : #1 => ", arg1); 16. Print(__FUNCTION__, " : #2 => ", arg1 + 3); 17. } 18. //+------------------------------------------------------------------+
代码 06
这样,我们现在就有了类似于代码 03 的功能代码。但我们不是通过值传递,而是通过引用传递。运行代码 06 的结果将与图 01 相同。
这让我们回到了本主题开头提出的观点 — 避免通过引用来修改单个变量。
如果出于任何原因,您需要一个例程来修改变量的值,特别是基本类型,您应该更喜欢使用函数而不是过程。这就是编程语言中存在函数的原因。创建函数不仅仅是为了使代码美观,它们的存在是为了避免代码中的问题。
现在,如果您确实希望修改 “info”,但是从例程而不是直接从声明的地方进行修改,那么正确且最合适的方法类似于下面显示的方法:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. int info = 10; 07. 08. Print(__FUNCTION__, " : #1 => ", info); 09. info = Function(info); 10. Print(__FUNCTION__, " : #2 => ", info); 11. } 12. //+------------------------------------------------------------------+ 13. int Function(const int & arg1) 14. { 15. Print(__FUNCTION__, " : #1 => ", arg1); 16. 17. return arg1 + 3; 18. } 19. //+------------------------------------------------------------------+
代码 07
请注意,代码 07 仍然使用通过引用传递。但它以完全可控的方式使用它。正如您在第 09 行看到的,其中 “info” 被明确修改。当以这种方式编写时,使用函数,这不太可能导致问题。运行代码 07 的结果如下所示:
图 04
看到了吧?我们绝不会想知道发生了什么。只需简单查看代码,即可清楚地了解为什么在 OnStart 块内修改信息。小心点,别做那种事。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. int info = 10; 07. 08. Print(__FUNCTION__, " : #1 => ", info); 09. info = Function(info); 10. Print(__FUNCTION__, " : #2 => ", info); 11. } 12. //+------------------------------------------------------------------+ 13. int Function(int & arg1) 14. { 15. Print(__FUNCTION__, " : #1 => ", arg1); 16. 17. return (arg1 = arg1 + 3) + 3; 18. } 19. //+------------------------------------------------------------------+
代码 08
对于一些程序员来说,怀着目标尝试这样的事情是很常见的,但最终却造成了真正的混乱。代码 08 就是这样一个例子。虽然这不是一个完美的例子,因为这些错误经常发生在涉及许多变量和步骤的更复杂的操作中。无论如何,它有助于说明这个问题。
亲爱的读者,我想问您一个问题:代码 08 第 10 行要打印的 “info” 值是什么?您可能没有注意到,但 arg1 不再是一个常量。那么第 10 行应该输出什么信息值呢?您可能会感到困惑,因为第 17 行同时修改了返回值和赋给 “info” 的值。在解释为什么这并不那么令人困惑之前,请检查如下所示的输出:
图 05
请注意,该值是 16,而不是 13。为什么呢?因为虽然 “arg1” 是指向 “info” 的指针,并且第 17 行将 13 分配给两者,但函数的返回值会覆盖它。发生这种情况是因为它在第 17 行赋的值上又增加了 3。因此,打印的输出实际上是 16,因为函数的返回被赋值给变量 “info”。
但是,如果您没有在第 9 行将返回值赋给 “info”,而是简单地忽略了函数返回的值,那么您将不可避免地陷入我们在前面的示例中观察到的相同情况。换句话说,“info”的值会被修改,但是当您尝试理解和调试代码时,您可能会花费大量时间来确定问题所在。请记住,包含此类错误的代码通常分布在多个文件中,每个文件都有数百行代码。因此,找到此类问题的根本原因可能非常耗时。
关于返回值,完全忽略它们并不罕见。毕竟,我们没有义务在代码中分配甚至使用返回值。因此,在程序中使用这种构造时要小心。尽管通过引用提供了极大的灵活性,但您最终会面临这种方法产生的问题既不困难也不太可能,特别是如果您仍在积累编程经验。
现在,作为这里要解决的最后一点:使用引用传递时经常出现另一个问题。它通常源于向处理参数的例程传递参数时的错误。在处理按值传递时,意外地将一个参数替换为另一个参数通常对整个代码的影响很小。在编译过程中,编译器通常会发出警告,特别是在预期的数据类型不同的情况下。但是,当使用引用传递时,如果数据类型兼容并且您忽略了参数顺序不正确的事实,编译器可能会警告您不匹配。尽管如此,你可能会犯一个以后更难识别的错误。例如,假设你想创建一个函数,在其中传递日期和时间(以小时为单位)。该函数应将指定的小时数添加到日期中,在同一函数中,您希望将这些小时数转换为秒,并更新相应的变量。这似乎是一个简单实用的任务,可以用以下代码实现:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. uint info = 10; 07. datetime dt = D'17.07.2024'; 08. 09. Print(__FUNCTION__, " : #1 => ", info, " :: ", dt); 10. dt = Function(dt, info); 11. Print(__FUNCTION__, " : #2 => ", info, " :: ", dt); 12. } 13. //+------------------------------------------------------------------+ 14. datetime Function(ulong &arg1, datetime arg2) 15. { 16. Print(__FUNCTION__, " : #1 => ", arg1, " :: ", arg2); 17. 18. return arg2 + (arg1 = arg1 * 3600); 19. } 20. //+------------------------------------------------------------------+
代码 09
但是,当请求编译器构建可执行文件时,它会发出如下所示的警告。这种警告不会阻止代码编译:
图 06
但是,由于您是一名始终关注编译器生成的每条消息的程序员,因此您会立即查看警告中引用的行并进行必要的更正。在这种情况下,只需将值从 ulong 明确转换为 datetime 即可。重要细节:两种数据类型具有相同的位宽。这就是为什么许多人倾向于忽视这些警告。因此,代码 09 的第 18 行被调整如下。
return arg2 + (datetime)(arg1 = arg1 * 3600);
现在编译器警告已经消失,您可以继续运行程序。但令你惊讶的是,输出如下所示:
图 07
此时,您可能会感到完全困惑,因为代码似乎是正确的,但它不起作用。关键细节在于:你的代码并非完全错误。它只包含一个微妙的错误。这种错误往往最难识别。在这里,更容易发现问题,因为代码简单明了,而且我们只是在演示基本概念。在实际场景中,你很少编写和测试小的孤立代码片段。通常,您开发程序中相互作用的各个部分。稍后才开始测试,看看是否一切正常。有时你会注意到一个问题,但有时你不会。在复杂代码中查找错误之所以如此具有挑战性,是因为相同的例程在某些情况下可能会导致错误,但在其他情况下却能完美工作。这使得调试变得极其困难。
最后,让我们找出错误所在。缺陷正好在第 10 行。现在,你可能会想:“拜托,错误怎么会出在第 10 行呢?错误肯定发生在从第 14 行开始的例程中,也可能发生在第 18 行。它不可能在第 10 行。”
但这就是你错的地方,亲爱的读者。让我们仔细分析一下。您希望 “info” 以秒为单位保存最终值。您希望 “dt” 变量以第 7 行声明的日期开始,但由基于 “info” 的函数进行调整。此调整必须在子程序的第 14 行,即第 18 行进行。到目前为止,一切都很好。然而,代码中存在一个小而关键的错误。由于 datetime(8 字节)和 ulong(也是 8 字节)具有相同的大小,因此该函数不会抱怨数据类型的差异。但 “info” 被声明为uint。你可能认为这就是问题所在,但事实并非如此。因为 24 小时等于 86400 秒,而占用 4 个字节的 uint 可以轻松存储这个值。虽然您可以使用较小的数据类型,但这会有溢出的风险。
现在,由于 “arg1” 是一个指针(因此使用通过引用传递)并且 “arg2” 通过值传递,错误真正在于第 10 行,其中第一个参数应该是变量 “info”(因为它将被修改)。第二个应该是 “dt”,它将根据函数的返回值进行调整。考虑到这一点,您应该将第 10 行替换为以下更正版本:
dt = Function(info, dt);
在尝试编译此更正的代码时,编译器将引发如下错误:
图 08
现在,您感到沮丧并迫切希望代码能够正常工作,并且意识到您实际上并不需要 8 字节的值(因为 4 个字节就足够了),因此您决定修改第 14 行,如下所示。
datetime Function(uint &arg1, datetime arg2)
最后,代码编译成功。再次运行它时,将会出现预期的正确输出,如下所示:
图片 09
最后的探讨
在本文中,我们探讨了现实世界编程中微妙但关键的细微差别。当然,这里的所有例子都很简单,是为了教育目的而设计的。尽管如此,解释事情到底是如何运作的,既令人愉快又有价值。我知道很多人可能会觉得这种材料“太基础”或不是特别令人兴奋,尤其是那些已经编程很长时间的人。但让我问你:你花了多少时间学习这里以如此简单的方式解释的概念?就我个人而言,我花了很长时间才弄清楚为什么我的 C/C++ 代码有时会表现出奇怪和难以解释的行为,直到我最终理解了所有底层细节。
一旦你掌握了这些基本原理,其他一切都会变得更简单、更直观。今天,我用各种语言编程很有趣,享受每种语言给我带来的挑战。这是因为我从 C/C++ 打下了坚实的基础。与 C/C++ 固有的所有复杂性相比,MQL5 是一种更加令人愉快、更简单、更容易解释的语言。但是,亲爱的读者,如果您真正理解本文中的解释和演示,您将能够更快地学习如何创建优秀的 MQL5 应用程序。在下一篇文章中,我们将最终开始研究更多有趣和实用的内容。回头见!
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/15345



您好。文本中的不准确之处(用红线划线)
问候,弗拉基米尔
您好。文本中的不准确之处(用红线划出)
问候,弗拉基米尔
谢谢,已更正