
从基础到中级:IF ELSE
概述
此处提供的内容仅用于教育目的。在任何情况下,除了学习和掌握所提出的概念外,都不应出于任何目的使用此应用程序。
在上一篇文章,从基础到中级:按值或按引用传递中,我们对您在不同程序之间传输数据时应采取的概念、风险和预防措施进行了实用而客观的讲解。
基于该讨论以及之前涵盖的内容,我们现在可以开始探索更高级的主题。这是因为,在编程本身中,我们不仅仅使用数学表达式。这样做不仅会极大地浪费计算机提供的计算能力和分解能力,还会限制我们真正实现的可能性。
因此,理解从这一点开始将要解释的内容的前提是牢牢掌握前几篇文章中提出的概念。如果在任何时候你发现自己不确定为什么会发生某些事情,请参阅这些文章并仔细研究。避免匆忙完成学习过程,按照所提供的步骤进行操作。
话虽如此,在我们开始讨论本篇文章和未来文章中涵盖的几个主题中的第一个之前,有必要解释一个在讨论陈述时经常出现的术语。那么,让我们继续讨论本文的第一个主题。
定义什么是例程
每种编程语言都有特定的术语定义。为了确保对所提供的解释有清晰的理解和准确的解释,我们必须首先定义一个基本术语。
图 01
如图 01 所示,一段代码以一个开始大括号开始,以一个结束大括号结束。这个区块内的所有内容都应被视为一个单一实体。无论行数多少,也不管开括号和闭括号之间包含什么,你都应该把它看作一个有凝聚力的单元。这个单元就是我们所说的例程。
现在,请密切注意!例程可以是类似于下面所示的代码块。
{ ExtAOBuffer[i] = ExtFastBuffer[i] - ExtSlowBuffer[i]; double sumAO = 0.0; for(int j = 0; j < FAST_PERIOD; j++) sumAO+=ExtAOBuffer[i - j]; ExtSMABuffer[i] = sumAO / FAST_PERIOD; ExtACBuffer[i] = ExtAOBuffer[i] - ExtSMABuffer[i]; if ( ExtACBuffer[i] >= ExtACBuffer[i - 1]) ExtColorBuffer[i] = 0.0; // set color Green else ExtColorBuffer[i] = 1.0; // set color Red }
它也可以是下面给出的任何表达式。
ExtACBuffer[i] = ExtAOBuffer[i] - ExtSMABuffer[i];
即使是执行函数或过程的调用也属于这一类。换句话说,所有这些元素都是我们所说的例程。因此,每当我们提到将执行例程时,请理解这可能指从函数或过程调用到整个代码块的任何内容。此代码块可能包含多个表达式、过程或函数调用,甚至嵌套在原始例程中的其他例程。简而言之,将你的思维扩展到无限和超越!既然我们已经定义了这个术语,我们就可以开始了。
IF 语句:终极控制
在我们深入探讨之前,我想先做一个小免责声明。虽然编程语句必须用英语给出,但亲爱的读者和未来的程序员,并不严格要求您使用基于英语的语句。有办法解决这个问题。然而,就目前而言,让我们假设您刚刚开始编程。不要太担心具体的术语,专注于理解概念。掌握概念比简单地记忆语句列表或语法规则更有价值。
但为什么我在本节标题中将 IF 语句称为“最终控制”呢?为什么我要从 IF 开始?我们不能从另一个语句开始吗?嗯,是的,我们可以。然而,如果你真正理解 IF 语句的工作原理,并应用前几篇文章中解释的内容,你几乎可以在编程中做任何事情。我之所以说几乎是因为稍后还有另一个重要的概念要介绍。但是,如果你首先用更简单的命令建立一个坚实的基础,这个概念将更容易掌握,我们很快就会探索。如果你掌握了必要的概念并正确地应用它们,你就可以只使用 IF 语句、其他运算符和变量来创建任何程序。就是这些,没别的了。听起来不可能,不是吗?但我向你保证,这是真的。随着时间的推移,你会发现这是真的。
现在,让我们从理解 IF 语句的一个基本方面开始。IF 的字面意思是“如果”。每当你遇到这个语句时,就把它读作“如果”。这将使我们更容易理解其含义。
执行时,IF 语句会评估条件。这个条件必须始终被理解为真(true)或假(false),总是如此。测试或分析的内容并不重要。决定 IF 是否执行其中的例程的是测试条件的评估结果是真还是假。如前几篇文章所述,如果一个条件的值为零,则该条件被视为假,如果其值不是零,则被视为真。与许多人的看法相反,IF 语句不一定需要大于、小于或等于等比较运算符。它只需要确定条件是真还是假。没别的了。
为了使这个概念更容易理解,让我们来看一个简单的例子。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. char info = 10; 07. 08. Print(__FUNCTION__, " : #1 => ", info); 09. 10. if (info) 11. Print("True condition..."); 12. 13. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 14. } 15. //+------------------------------------------------------------------+
代码 01
当您执行此代码时,您将在 MetaTrader 5 中看到类似的内容:
图 02
在这里,我们正在应用前面解释的内容。在第六行,我们定义了一个变量。紧接着,在第八行,我们打印一条消息来分析我们数据的当前状态。但是,在第十行,我们引入了一个控制流语句:这也就是 IF 语句。现在,亲爱的读者,请密切注意。如果“info”等于 true,则将执行 IF 语句内的例程。如果“info”为 false,则不会执行 IF 语句内的例程,而是继续执行代码中的下一个例程。通过这种方式,我们可以控制和指导代码的执行流程。本例中的下一个例程位于第 13 行。
由于“info”的值不为零,因此执行 IF 语句内的例程。因此,我们看到了图 02 所示的输出。关于您应如何撰写此语句,有一些重要的注意事项。其中一些是强制性的,而其他则是可选的。但是,IF 语句的基本语法如下:
图 03
由此,您可以清楚地看到语句是如何执行的。语法简单易懂。
现在,如果我们不想在条件为真时执行 IF 语句中的例程,而是在条件为假时执行呢?这可能吗?这是一个非常好的问题!这正是许多初学者程序员挣扎的地方,有时甚至会感到沮丧。发生这种情况是因为他们不理解该语句背后的概念。我再强调一次:仅当条件为真时, IF 语句才会执行其中的例程。否则, IF 语句内的例程将不会被执行。“但是请等一下!我见过这样的代码,即使值为假,IF 语句内的例程也会执行。我认为你不知道自己在说什么。”好吧,我们不要争论这个。相反,让我们来测试一下。我们将创建一个简单的程序来验证这是否真的会发生。为了做到这一点,我们将故意强制一个条件,即 IF 语句中的表达式始终计算为真。其代码如下所示。这怎么可能?您可能已经看到即使值为 false,IF 语句中的例程也会被执行的代码。你可能会认为我不知道我在说什么。
我们不要争论这个。相反,让我们做以下事情:我们将创建一个简单的程序来验证这是否真的会发生。为此,我们将故意强制 IF 语句中的表达式始终为 true 的条件。其代码如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 07. 08. if (true) 09. Print("True condition..."); 10. 11. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 12. } 13. //+------------------------------------------------------------------+
代码 02
现在你可以看到,第 8 行中的测试将始终为真。因此,无论代码执行多少次,第 9 行总是会被执行。第 9 行将始终被执行,结果如下所示:
图 04
前面提到过,您已经看到过 IF 语句在表达式为 false 时运行例程。好的,让我们让表达式为 false,看看会发生什么。这在以下代码中显示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 07. 08. if (false) 09. Print("True condition..."); 10. 11. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 12. } 13. //+------------------------------------------------------------------+
代码 03
请注意,我所做的唯一修改是第八行 IF 语句中的表达式。然而,在执行代码时,输出如下图所示。
图 05
等等…… 图 04 中出现的另一行在哪里?它去哪儿了?好吧,亲爱的读者,图 05 中缺少它,因为它从未被执行过。发生这种情况是因为第八行测试的表达式为 false。而且它永远都是 false 的。由于条件为 false,IF 语句内的例程将永远不会被执行。然而,这里有一个小而关键的细节,一个被许多人忽视的细节。也许这就是为什么你可能认为 false 条件仍然可以触发 IF 语句中的例程。这种误解通常发生在表达式的结果以某种方式反转时,这意味着以前为 false 的东西变成了 true,反之亦然。
请密切关注这一点。您编写代码的方式可以决定最终值是 true 还是 false。这种问题经常让程序员感到沮丧,因为乍一看,一个表达式可能看起来是正确的。但是,由于通常与运算符优先级相关的内部因素,表达式可能会产生意外结果。问题不在于数学错误,而在于 IF 语句上下文中的逻辑错误。这是一个非常重要的主题,值得用一篇文章来专门描述它。然而,由于它涉及使用额外的语句和运算符来充分理解它,我现在将推迟讨论。
同时,您可以通过参考文档来研究此主题:优先规则。
即使不深入这些规则,我们也可以对代码进行简单的修改。即使表达式看起来为 false,此更改也将允许第八行 IF 语句内的例程执行。这是通过修改代码实现的,如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 07. 08. if (~false) 09. Print("True condition..."); 10. 11. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 12. } 13. //+------------------------------------------------------------------+
代码 04
通过进行这种几乎难以察觉的更改,我们得到了如下所示的结果:
图 06
乍一看,当不小心查看代码 04 时,我们可能会认为测试的表达式是错误的。但是,请注意保留字 false 前面的符号。大多数情况下,您会看到使用了感叹号。但还有许多其他方法可以实现这一点。例如,知道测试总是检查值是否等于零,您可以编写如下内容:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 07. 08. if (false | true) 09. Print("A very crazy condition always true ..."); 10. 11. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 12. } 13. //+------------------------------------------------------------------+
代码 05
执行此代码后,您将看到下面显示的输出:
图 07
我可以继续展示多种变体,但没有什么比在充分理解正在发生的事情的同时自己进行实验更好的了。有了这个,让我们考虑一下所涵盖的 IF 语句,并继续讨论下一个控制结构 — 如果没有IF,这个结构甚至不存在。我指的是 ELSE 语句。我们将在下一节中探讨这个问题。
ELSE 语句,它并不孤单
初学者的一个常见错误可能是由于他们使用的学习材料的类型,即试图单独使用 ELSE 语句。ELSE 语句可以解释为“否则”,必须始终与另一个语句配对。ELSE 之前的语句取决于编程语言。许多人认为 ELSE 总是与 IF 语句相关联,但在 SQL 中,ELSE 可以与 CASE 语句相关联。这可能看起来很奇怪,但这就是语言的设计方式。所以,不要以为知道一种编程语言会自动使你有资格用另一种编程。虽然有些概念是可转移的,但每种语言都有其必须理解的细微差别。
如果你掌握了 IF 语句,理解 ELSE 就会简单得多。当与IF一起使用时,ELSE 块仅在 IF 条件为 false 时执行。这基本上就是全部。关于这一点,没有什么可说的了。
然而,让我们来看看执行流程,这将使事情变得更加清晰。流程图如下:
图 08
查看图 08 后,您会注意到,只要存在 ELSE 语句,它总是链接到 IF 语句。虽然这个概念起初看起来很简单,但在实践中,它并不总是那么简单。亲爱的读者,在将 IF 语句与 ELSE 块一起使用时,必须采取某些预防措施。我们将在本文稍后介绍这些。但在此之前,为什么不尝试修改前面的代码示例以包含 ELSE 语句呢?我不会修改每个例子,因为考虑到我们到目前为止所涵盖的内容的简单性,这是不必要的。相反,让我们采用其中一段代码并通过添加 ELSE 块来修改它。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. int info = 10; 07. 08. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway... #1 => ", info); 09. 10. if (info) 11. Procedure(info); 12. else 13. Print("Variable info is equal to zero..."); 14. 15. if (info) 16. Print("True condition..."); 17. else 18. Print("False condition..."); 19. 20. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway... #2 => ", info); 21. } 22. //+------------------------------------------------------------------+ 23. void Procedure(int & arg1) 24. { 25. Print(__FUNCTION__, " ", __LINE__, " #1 => ", arg1); 26. arg1 = 0; 27. Print(__FUNCTION__, " ", __LINE__, " #1 => ", arg1); 28. } 29. //+------------------------------------------------------------------+
代码 06
代码 06 乍一看似乎很复杂,但实际上比乍一看要简单得多,也更清晰。当我们运行代码 06 时,我们将得到如下所示的结果:
图片 09
看看图片 09,你可能会想:“需要处理的信息太多了!”然而,这正是为什么你应该更有动力集中注意力,深入探究。让我们一步一步地分解,以便更好地了解如何有效地使用 IF-ELSE 组合。
首先,在第 6 行中,我们创建了一个将在整个代码中使用的变量。第八行打印图 09 中的第一行输出。如果你已经理解了 IF 语句,那么你已经知道第 10 行会发生什么。此时,我们可以执行或不执行 Procedure 例程。要理解这里发生了什么,您需要理解我们在之前讨论变量的文章中解释的内容。最终,执行到达第 15 行,在此可以执行两个例程之一。无论哪种方式,我们都会向终端输出一条新消息。最后,我们有第 20 行,它打印终端中可见的最后一条消息。
将附上此代码 06,以便您可以进行小修改实验。以下是一些调整代码的建议。您可以更改第 6 行中的变量值,以便第 10 行中的测试会导致执行不同的例程。进行此更改后运行代码以了解其行为。然后,您可以修改 IF 条件以更改消息的打印方式。调整 Procedure 例程内的条件,使第 15 行的 IF 语句表现出不同的行为。每次做出更改时,观察结果并分析其行为方式的原因。这种练习将帮助你牢牢掌握 IF 语句。这对于理解未来文章中涵盖的控制结构至关重要。
但是,使用 IF ELSE 组合的时候有一个小问题。为了解决这个问题,让我们从一个新的话题开始。
嵌套问题
当广泛使用 IF-ELSE 组合时,经常会出现嵌套问题。当多个 IF-ELSE 块相互嵌入,产生级联条件时,就会发生这种情况。然而,当仅嵌套 IF 语句时,不会出现此问题,这是引入 ELSE 时出现的一个问题。也就是说,经验丰富的程序员也会避免过度嵌套 IF 语句,因为这通常会导致代码不可读。这种设计没有多大意义。
为了帮助您直观地理解我所说的嵌套和级联,请考虑以下示例:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. char c1 = 10, 07. c2 = 0, 08. c3 = -6, 09. c4 = 3; 10. 11. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 12. 13. if (c1) 14. if (c2) 15. if (c3) 16. if (c4) 17. Print("True condition..."); 18. 19. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 20. } 21. //+------------------------------------------------------------------+
代码 07
这是嵌套 IF 语句的典型示例。你可能会想,“这样的编码有什么问题?”从技术上来说,没什么。然而,经验丰富的程序员很少以这种方式编写代码。相反,他们通常使用逻辑运算符重写它,产生功能等效但更可读的版本。Code 07 的一个可能替代方案如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. char c1 = 10, 07. c2 = 0, 08. c3 = -6, 09. c4 = 3; 10. 11. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 12. 13. if ((c1) && (c2) && (c3) && (c4)) 14. Print("True condition..."); 15. 16. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 17. } 18. //+------------------------------------------------------------------+
代码 08
观察代码 07 和代码 08 的输出将相同。然而,在代码 08 中,代码 07 中的嵌套 IF 语句已被一系列逻辑运算所取代。在这种情况下,我们使用 AND(与) 运算。任何其他类型的操作都会改变代码 08 的行为,导致它不会生成与代码 07 相同的结果。如果你不确定你的代码是否按预期运行,试着用不同的格式重写它以确保清晰。
然而,嵌套问题不仅仅出现在 IF 语句的级联中。它在 IF 和 ELSE 组合中尤其常见。但这样的问题是如何产生的呢?我还是不太明白。因此,为了理解这一点,我们将使用类似于代码 07 的内容,但添加一个 ELSE 语句。最终代码如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. char c1 = 10, 07. c2 = 0, 08. c3 = -6, 09. c4 = 3; 10. 11. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 12. 13. if (c1) 14. if (c2) 15. if (c3) 16. if (c4) 17. Print("True condition..."); 18. else 19. Print("False condition..."); 20. 21. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 22. } 23. //+------------------------------------------------------------------+
代码 09
乍一看,代码 09 与代码 07 非常相似。然而,在代码 09 中,我们遇到了一个问题,这是一个很大的问题。如果你还没有意识到问题的严重性,那么你肯定需要这些文章来帮助你更好地理解如何构建你的程序。
这里的问题正是第 18 行出现的 ELSE。我来问你一下:这个 ELSE 属于哪一个 IF 语句?
由于缩进,您可能会认为第 18 行的 ELSE 与第 14 行的 IF 相关联。虽然一些编程语言确实以这种方式解释它,但许多语言没有这样做。实际上,在绝大多数情况下,缩进不是作为函数代码的一部分使用的,而是作为一种组织它的方式,使其更具可读性。
在 MQL5 中,缩进不会影响代码执行;它只会使代码更具可读性。第 18 行的 ELSE 代码以其当前形式链接到第 16 行的 IF。
为了解决这个问题而不使其过于复杂,并解释如何解决它,我将展示两种可能的解决方案。其中一个是大多数程序员的首选,而另一个只有少数人使用。让我们从第一个开始,这是大多数程序员最喜欢的。这个解决方案包括将代码划分为块,这让编译器清楚地知道 — 这正是我们想要做的。它还使程序更具可读性和可理解性。该解决方案如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. char c1 = 10, 07. c2 = 0, 08. c3 = -6, 09. c4 = 3; 10. 11. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 12. 13. if (c1) 14. if (c2) 15. { 16. if (c3) 17. if (c4) 18. Print("True condition..."); 19. } 20. else 21. Print("False condition..."); 22. 23. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 24. } 25. //+------------------------------------------------------------------+
代码 10
实际上,这是一个很好的解决方案。请注意,在第 14 行中,我们有一个 IF,我们想要将其链接到 ELSE。为了确保正确定义第 14 行 IF 中的表达式为真时将执行的子例程被正确定义,我们将其放置在一个块中。由于我们嵌套了整个 IF 序列,因此从第 15 行开始到第 19 行结束的块实际上是一个子例程,应该在第 14 行的 IF 中执行。此块甚至可以放置在 OnStart 块之外的函数或过程中。
亲爱的读者,理解这一点非常重要。如果第 14 行的 IF 语句计算结果为 false,程序将直接跳转到 ELSE 语句来执行其包含的子程序。请注意,正在执行的子例程可以是任何东西,因此理解语句而不仅仅是记住语法或编程风格非常重要。
请注意这一事实,如果您真正理解 IF 语句并知道如何将其与 ELSE 语句组合,则可以对代码 09 使用另一种解决方案。然而,这种类型的解决方案并没有得到广泛的接受,因为在许多情况下,它并不能使代码真正易于理解。但由于我们正在处理教学材料并解释如何进行,我们可以将第 18 行的 ELSE 与第 14 行的 IF 链接起来。为此,我们只需将其他 ELSE 语句与其他 IF 语句链接起来,如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. char c1 = 10, 07. c2 = 0, 08. c3 = -6, 09. c4 = 3; 10. 11. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 12. 13. if (c1) 14. if (c2) 15. if (c3) 16. if (c4) 17. Print("True condition..."); 18. else; 19. else; 20. else 21. Print("False condition..."); 22. 23. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 24. } 25. //+------------------------------------------------------------------+
代码 11
请注意,在代码 11 中,识别第 14 行的 IF 与现在的第 20 行的 ELSE 之间的联系并不那么简单和清晰。要理解这一点,您需要仔细分析代码。然而,代码 10 和代码 11 在执行时将产生相同的结果。也就是说,如果第 14 行的 IF 为 false,那么将执行相应 ELSE 中的子程序。
最后的探讨
在本文中,我们介绍了控制流语句。我知道这些概念一开始可能看起来很混乱。但是,只要有耐心和持续的练习,当你为遇到的问题找到新的解决方案时,你就会逐渐掌握 — 只有少数人能做到。
然而,为了实现这一目标,有必要不断寻找解决同一问题的最佳方案。一个问题已经解决的事实并不意味着没有其他更有效的解决方案。总是有更好的方法。你仍然需要学会超越显而易见的东西。
最后,附件中不会包含您在此处看到的所有代码,只有其中的一部分可用。但是,在阅读完本文后,您仍然可以进行任何必要的更改,以便最终获得此处提供的所有代码。除了是很好的学习实践外,这些材料还将帮助你找到自己的风格。毕竟,即使代码相同,编写方式也可能因程序员及其风格而异。所以,是时候定义你的编程风格了。寻找方法使您的代码可供其他程序员使用,同时帮助您快速创建和修改自己的解决方案。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/15365



