
从基础到中级:SWITCH 语句
概述
此处提供的内容仅用于教育目的。在任何情况下,除了学习和掌握所提出的概念外,都不应出于任何目的使用此应用程序。
在上一篇文章“从基础到中级:Include 指令“中,我们介绍了使用 #include 编译指令的基础知识。那篇文章是一个简短的停顿,让你能够正确地吸收信息,并花时间研究如何使用控制流语句。这是因为学习并彻底了解如何使用它们确实很重要。然而,我们还没有完全完成。在 MQL5 中,我们还需要涵盖另外两条语句。它们有些复杂,主要是在使用它们时需要注意的地方。
因此,我将更加谨慎地对待这两个语句。这一点很重要,因为作为一名程序员,在使用这两个最终运算符时,即使是最轻微的错误,在试图识别代码中的缺陷时,也几乎肯定会导致大量时间的浪费。
如果你发现前面的命令具有挑战性和复杂性,请做好准备 — 最后两个命令需要更多的关注。然而,在本文结束时,您至少会了解每一个功能是如何工作的。因此,跟随本文的第一个先决条件是能够熟练地使用变量和常量,以及理解如何使用 IF 语句。如果您对这些领域有信心,您将准备好继续并理解此处提供的内容。
按照惯例,我们开始一个新的话题。我们将开始讨论倒数第二个要解释的语句。
SWITCH 语句
该语句的字面意思是“切换” — 可以作为 IF 语句的可能替代。但是,在有效地使用 SWITCH 语句代替 IF 之前,需要了解一个重要的细微差别。
亲爱的读者,要理解这种替代何时以及如何可能,重要的是你首先要理解一个关键概念。在编程中,经常会遇到这样的情况,即我们多次测试同一变量以搜索一个非常具体的值。“非常具体”这个短语是这里的核心思想。该值不能大于、小于或相似;它必须正是我们要寻找的值。不多不少:正正好好。
在这种情况下,我们可以避免使用多个 IF 语句,而是使用单个 SWITCH 语句。然而 - 这很关键 - 您需要意识到 SWITCH 语句中使用的变量的位宽和格式在它是否能被有效使用方面起着重要作用。
总之:只有当且仅当在这些 IF 条件下检查相同的变量时,您才能使用 SWITCH 作为多个 IF 语句的替代。此外,您只能检查变量是否等于某个值。无法检查它是否大于或小于该值。此外,变量的位宽也会影响评估结果。用非常简单的术语来说,这就是定义 SWITCH 语句如何工作的原因。
乍一看,这似乎过于复杂,或者至少不切实际。然而,事实却恰恰相反。SWITCH 语句在许多不同的场景中被广泛使用。尽管它最初可能看起来有限或繁琐,但它在许多现实世界的应用中非常有用。
真正让 SWITCH 语句有些令人困惑或令人生畏的是,它在各种编程语言和上下文中的行为不同,尤其是对于初学者程序员来说。也就是说,在本文中,我们将特别关注如何在 MQL5 中使用 SWITCH 语句。在此上下文中,它的行为与 C/C++ 中的 SWITCH 语句非常相似。您甚至可以参考 C/C++ 文档作为补充资源来加深对此结构的理解。这是因为我们在这里要介绍的只是基础,介绍一下语句是如何运作的,以及需要记住的一些预防措施。如果您研究如何在 C/C++ 中实现 SWITCH,就会发现还有很多东西值得探索。
好吧,我相信这能让你很好地了解未来会发生什么,以及如果你愿意的话,你可以在哪里进行更深入的挖掘。因此,现在是时候深入探讨 SWITCH 语句及其潜在用途了。首先,我们将从一个非常简单的例子开始,这个例子还没有使用语句本身。我们来看看下面的代码:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. uchar counter = 5; 07. 08. if (counter == 5) Print("The counter value is five."); else 09. if (counter == 3) Print("The counter value is three."); else 10. if (counter == 1) Print("The counter value is one."); else 11. Print("The counter value is unknown."); 12. } 13. //+------------------------------------------------------------------+
代码 01
我知道很多读者看到这个会说:“伙计,这真是一段愚蠢的代码。”是的,我知道,确实如此。但我们确实需要从一些非常简单的事情开始。否则,您最终可能会完全迷失方向,无法理解随后发生的事情。
只需查看此代码,您就可以清楚地了解 MetaTrader 5 终端中显示的内容。因此,我认为完全没有必要显示输出消息。现在,亲爱的读者,如果你不能通过阅读代码来理解它,那么我强烈而毫不犹豫地建议你停在这里,倒退,从第一篇文章重新开始:从基础到中级:变量(一) 。不要以为跳过步骤就能学会编程,你不会的。在尝试向陌生领域迈出更大的一步之前,首先要了解基本概念和命令。
好吧,我们继续吧。此时,您可以清楚地看到,这段代码准确地演示了我们在本主题开头所定义的内容。也就是说,我们有一个变量通过多个 IF 语句进行求值,每个都在检查一个特定的值。我们不是在检查这个值是大于还是小于某个值;我们正在寻找精确的匹配。这才是关键所在。换句话说,一旦我们遇到了代码 01 中所示的情况,我们就可以用更优雅的东西来替换它。这就是 SWITCH 语句。
现在,这一点已经明确定义并得到了很好的理解,让我们看看另一个代码示例,它在执行时会产生完全相同的结果。如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. uchar counter = 5; 07. 08. switch (counter) 09. { 10. case 5: 11. Print("The counter value is five."); 12. break; 13. case 3: 14. Print("The counter value is three."); 15. break; 16. case 1: 17. Print("The counter value is one."); 18. break; 19. default: 20. Print("The counter value is unknown."); 21. } 22. } 23. //+------------------------------------------------------------------+
代码 02
亲爱的读者,这就是你要的。代码 02 与代码 01 类似,产生完全相同的结果,输出没有任何区别。但你可能会想:“伙计,这看起来复杂多了。我宁愿坚持使用代码 01。”我不争辩,乍一看,代码 02 确实显得更复杂。那么为什么有人在编写程序时选择使用代码 02 而不是代码 01 呢?好吧,当我们以最基本的形式讨论 SWITCH 语句时,答案并不那么简单。但是,我可以向您保证,在许多情况下,SWITCH 比 IF 具有显著的优势。这是因为 SWITCH 具有 IF 所不具备的能力:在执行过程中处理其他值的能力。
现在,在使用 SWITCH 语句时,这种行为是一个更高级的主题。我只是在这里提到它,所以你不要仅仅因为 SWITCH 看起来令人困惑或复杂而直接忽略它。事实上,它比最初看起来要有用得多。
让我们仔细看看代码 02。你会注意到这里使用了一些有趣的元素。首先:这个 BREAK 语句在这里起什么作用?BREAK 通常不是在循环内使用吗?没错,亲爱的读者。BREAK 语句确实在循环中使用。但是,它也用于 SWITCH 语句中的特定点。其目的是控制 SWITCH 如何执行。更具体地说,当我们使用 BREAK 语句时,我们告诉编译器在该点停止执行当前的 SWITCH 例程。这样,编译器就确切地知道下一步该做什么。从某种意义上说,这类似于 BREAK 在循环中的工作方式。但在这里,它的应用方式不同,取决于执行的结构。别担心,我们稍后会更详细地讨论这个问题。但在此之前,让我们谈谈你可能注意到的其他事情:保留关键字 CASE 的使用。如果仔细比较代码 01 和 02,您会发现这里有明显的相似性。
SWITCH 语句看起来类似于 IF,现在我们已经进入了一个代码块。CASE 这个词的作用很像代码 01 中每个 IF 表达式中使用的相等条件。在 CASE 之后,我们提供了一个值,该值与您在每个 IF 条件中看到的值完全相同。很有趣,不是吗?不,这不是巧合。这正是 SWITCH 语句的工作设计方式,以及在实际使用中应该如何理解它。这就是为什么从表面上看,SWITCH 可能看起来比实际复杂得多。但一旦你开始理解它是如何工作的,它就变得非常简单,事实上,它非常强大。你很快就会发现自己在想新的方法来应用它。
现在,在代码 01中,我们有第 11 行,当所有值都与预期值不匹配时执行。同样的事情也发生在 SWITCH 中。为了实现此行为,我们使用另一个保留关键字:DEFAULT。在 SWITCH 例程或代码块中,DEFAULT 告诉编译器,如果没有案例与 SWITCH 表达式中测试的值匹配,则应执行替代例程。通常,此子语句 DEFAULT 放在 SWITCH 块的末尾。这保留了逻辑结构。但是,从技术上讲,您可以将其放置在块内的任何位置。只是这样做会相当不合常规和尴尬。
很有趣吧?还有一个细节值得强调,这可能会使 SWITCH 的实用性更加清晰。这就是:IF 语句只会在表达式计算结果为 true 时执行与其关联的块。无论值是什么,只有当条件为 true 时,代码块才会运行。有了 SWITCH,事情就变得有点不同了。该表达式以数字方式进行评估。重要的是该值是否与我们正在测试的值匹配。CASE 块仅在值完全等于 SWITCH 语句中的表达式时执行。但是,除非遇到 BREAK 语句或到达 SWITCH 语句的末尾,否则该块不会结束。所以请注意这一点。
目前,在开始新的 CASE 之前,请务必包含 BREAK 语句。这将有助于保持代码的有序性并防止混淆。稍后,我们将探讨省略 BREAK 可能有用的场景。但在那之前,请养成在每个新 CASE 之前都加入一个 BREAK 的习惯。
好了,说了这么多,我们来看看 SWITCH 语句的执行流程到底是怎么样的。如下图所示:
图 01
亲爱的读者,在这里,我们看到的与我们在前面的执行流程图中看到的有所不同。这是因为图 01 旨在说明您应该如何理解 SWITCH 语句。因此,如果你真的想了解 SWITCH 是如何工作的,特别是在我们深入了解更高级的方面之前,正确理解这个图非常重要。一旦您这样做了,您将能够轻松地探索 SWITCH 语句,甚至可以超越我们当前演示的水平。
以下是解释。正如您所注意到的,一切都像任何其他语句一样开始,即通过使用保留关键字来声明语句的开始。从那一刻起,直到我们到达出口点(末端的小圆圈),一切都完全属于 SWITCH 块。任何外部命令都无法访问它。理解这一点至关重要,尤其是对于我们稍后将探讨的概念。
现在,我们遇到的下一个元素是表达式。这个表达式应该用数字解释,而不是逻辑解释。这在实践中意味着什么?这意味着在表达式中使用“大于”或“小于”之类的条件不会产生您期望的结果。因此,与 IF 语句不同,其中关键因素是表达式的计算结果为 true 还是 false,而这里 SWITCH 中的表达式必须始终被视为产生一个数字。该数字将在下一步中使用。
下一步是 CASE 语句发挥作用。这是对表达式进行逻辑评估的地方。即,它将被评估为 true 或 false。但请等一等。似乎有些矛盾。您刚才不是说表达式不是按逻辑来评估,而是按数字来评估吗?现在我说这又合乎逻辑了。是的,这听起来像是一个矛盾。这是一个绝对合理的问题,因为起初一切似乎都很混乱。
不幸的是,没有更简单的方法来解释它。表达式的值被传递下来并根据各个 CASE 语句进行检查。没有严格的顺序。表达式和 CASE 值之间的联系纯粹是数字的。但是,当执行 CASE 语句时,它会从逻辑上检查值,即 true 或 false。这就是为什么我在每种情况旁边都添加了一个附加标签:数值。这说明了与表达式进行比较的实际值。如果匹配,我们就执行相应的 ROUTINE(例程)。要做出任何类型的决定,都必须进行逻辑比较,即使它是由数字匹配触发的。这就是事情变得有点混乱的地方,许多人错误地认为求值直接发生在 SWITCH 表达式中。但实际上,它是在具体 case 匹配过程中发生的。
我不是说过,如果你没有完全理解前面的文章,SWITCH 语句可能会有点令人困惑吗?而且我们仍在研究它的最基本形式!
无论如何,一旦 CASE 验证了 EXPRESSION 等于 VALUE,就会执行相应的 ROUTINE。句号,下一项,现在我们到了一切都变得复杂的部分。但这稍后将会解释。这就是您在图 01 中看到的神秘红线。这条线代表了我们暂时无法解决的更深层次的复杂性。我们暂时忽略它。除非你渴望自己探索未来,并弄清楚当流量通过那条红线时会发生什么,否则我建议暂时别管它。您应该这样做:在每个 ROUTINE 块的末尾插入一个 BREAK 语句。这将确保执行干净地退出 SWITCH 块。这句话很有趣,但如果你不小心,它真的会让你的大脑一团糟。除非你深入了解控制流,否则在 SWITCH 中编写逻辑的方法会变得非常难以理解。
好吧,到目前为止,一切听起来都很好。但有一个想法一直困扰着我。在前面的文章中,我们了解到 BREAK 语句允许我们退出循环,而无需重新评估循环条件。这完全说得通。但是现在,看到在 SWITCH 内部使用 BREAK,您可能会对某些事情感到有些困惑。如果我们在一个循环内并且我们也在该循环内使用 SWITCH,就像您向我们展示的那样,会怎么样?如果我们在 SWITCH 中使用 BREAK,这不会提前终止整个循环吗?亲爱的读者,这是一个很好的问题。这类问题表明你真的在跟随和理解我们到目前为止所涵盖的材料。因此,我们将采取以下措施:既然你已经知道如何创建循环并使用 BREAK、RETURN 和 CONTINUE 控制其流程,让我们来研究一个比本文开头看到的稍微高级一点的示例,
或者在更早的版本中。这里的目标是使内容更清晰,而不是构建一些特殊或功能性的东西,是以实用的、演示的方式看待概念。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. Print(__FUNCTION__, " ", __LINE__, " Counting..."); 07. Print(__FUNCTION__, " ", __LINE__, " Pit Stop :: ", Tic_Tac()); 08. switch (Tic_Tac()) 09. { 10. case true: 11. Print(__FUNCTION__, " ", __LINE__, " Return TRUE"); 12. break; 13. case false: 14. Print(__FUNCTION__, " ", __LINE__, " Return FALSE"); 15. break; 16. } 17. } 18. //+------------------------------------------------------------------+ 19. bool Tic_Tac(void) 20. { 21. static uchar counter = 10; 22. 23. while (true) 24. { 25. switch (counter) 26. { 27. case 8: 28. counter = counter - 1; 29. return false; 30. case 6: 31. Print("Press TAB to continue or ESC to abort the count."); 32. counter = counter - 1; 33. break; 34. case 5: 35. if (TerminalInfoInteger(TERMINAL_KEYSTATE_TAB)) 36. { 37. Print("Accelerating count."); 38. counter = 2; 39. } else if (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) 40. { 41. Print("General panic..."); 42. return false; 43. } 44. continue; 45. case 0: 46. return true; 47. default: 48. counter = counter - 1; 49. } 50. Print(__FUNCTION__, " ", __LINE__, " :: ", counter); 51. Sleep(300); 52. } 53. Print(__FUNCTION__, " ", __LINE__, "This line will never be executed."); 54. } 55. //+------------------------------------------------------------------+
代码 03
本代码 03 介绍了一些非常有趣的代码构造和实现元素。这是因为,在这里,我们几乎使用了到目前为止我们所涵盖的所有内容。然而,在深入了解代码 03 的细节之前,让我们先看看在 MetaTrader 5 终端中执行它时会发生什么。事实上,有两种可能的结果,每种结果都在下面的动画中演示。
动画 01
在第一个动画中,您可以观察到按下 ESC 键时会发生什么。现在,当我们按 TAB 键时,会发生一些稍微不同的事情,如下一个动画所示。
动画 02
请注意,这两个动画之间存在微妙的差异。但这里真正重要的是了解这些信息是如何到达终端的,以及流控制语句如何确定每条信息何时出现。
现在,亲爱的读者,我不会在这里详细介绍某些部分。这是因为我想鼓励您重新阅读前面的文章,以真正了解每个流控制语句是如何工作的。然而,我仍将概述代码 03 中的情况。
让我们从代码的入口点 OnStart 开始。第 6 行显示了我们在执行过程中所处的位置。这行代码一执行就打印出来。但是,第 7 行不会立即处理。因为它包含对 Tic_Tac 函数的调用。因此,第 7 行的完整执行被推迟,直到 Tic_Tac 完成其任务。
一旦进入 Tic_Tac 函数(第 19 行),我们就会在第 23 行进入无限循环。但别担心,亲爱的读者,这个循环在形式上是无限的,但我们有特定的点可以退出。请密切关注此处,以了解正在发生的事情。在第 25 行,我们进入流控制语句 SWITCH。对于每个相关的计数器值,我们执行一个特定的操作。请记住,计数器变量被声明为静态的。这非常重要。当计数器达到值八时,我们返回给调用者,返回值 false。此时,第 7 行已完全执行。这解释了终端上打印的消息。之后,我们转到第 8 行并再次调用 Tic_Tac 函数。但这次,我们使用它的返回值作为另一个 SWITCH 语句中的表达式。再次,执行将等待 Tic_Tac 完成。所以,我们回到第 19 行,从那里继续。
等一下,我们不是在第 29 行退出吗?您可能想知道:如果我们在第 29 行退出,那不是会重置所有内容并使第 23 行的循环在第 29 行再次结束吗?确实如此,亲爱的读者。但是因为第 28 行在返回给调用者之前执行,所以我们现在指向值 7。由于第 25 行 SWITCH 中的值 7 没有匹配的情况,执行跳到第 47 行。第 47 行包含一个仅执行第 48 行的例程,该例程将计数器减少一。现在,计数器指向值六。
无论哪种方式,完成 SWITCH 块后,我们继续执行第 50 行和第 51 行。这就是为什么某些值,比如 7,不会出现在终端中。打印值 6 后,循环重新开始,SWITCH 语句再次运行。现在表达式的值为 6,现在要注意了,在第 31 行,我们向终端打印另一条消息。然后,在第 32 行,我们将计数器设置为 5。但是在第 33 行使用 BREAK 语句时会发生什么?这就是许多初学者感到困惑的地方。从之前的文章中,我们知道 BREAK 会结束其所在的循环。这是真的,而且已经清楚地证明了这一点。然而,在这种特定情况下,break不会终止外循环。这是因为在这里,它是 SWITCH 语句的一部分。在这些情况下,BREAK 适用于 SWITCH,而不是周围的循环,无论是 WHILE、DO WHILE 还是 FOR。
这可能看起来很令人困惑,但为了证明这种行为,我在第 53 行放置了一条消息 — 这条消息永远不会在代码 03 中打印出来。尽管整个代码中唯一的 BREAK 出现在第 33 行。
我认为这应该很清楚。这段代码附在文章中,这样你就可以安心地学习它,直到使用 BREAK 运算符的这个小怪癖变得清晰为止。
一旦执行了第 33 行的 BREAK,我们就直接转到第 50 行。请记住,计数器在第 32 行减少了。所以现在它的值为 5。
循环再次运行,并再次检查 SWITCH。这次表达式的计算结果为 5。由于我们有一个该值的 CASE,因此它会被执行。这就是事情变得有趣的地方。再次强调,请密切关注。此时,程序等待用户按下计数器为 6 时显示的消息中提到的其中一个键。但是(并且这是关键点)如果用户没有按任何键,第 44 行将执行 CONTINUE 语句。现在问题出现了。由于 CONTINUE 始终引用封闭循环,因此执行会跳回到第 23 行。因此它完全跳过了第 50 行和第 51 行。只要表达式的结果为 5,这种情况就会一直持续。一旦用户按下某个键,流程就会再次改变。根据按下的键,我们要么转到第 38 行,将计数器设置为 2,要么转到第 42 行。无论如何,在某个时刻,无论是在第 42 行还是第 46 行,循环都会结束。然而,即使这样,第 53 行也永远不会被执行。然后我们返回第 8 行,此时,新的表达式被评估。但现在你可能会问:“等一下,switch 不是应该以数字方式评估表达式吗?为什么 case 值是布尔值?这对我来说不合理啊。”亲爱的读者,这正是重点所在。当我们讨论 IF 语句时,我解释了什么使得值变为 true 或 false。false 的概念保持不变。然而,当事情变为现实时,情况会有所改变。在这种情况下,第 10 行的 TRUE 值实际上是数值 1。因此,如果 Tic_Tac 函数返回 0 或 1,则第 8 行的 SWITCH 将识别它并显示相应的消息。但是如果将 Tic_Tac 的返回值更改为 0 或 1以外的值,则第 8 行的 SWITCH 将找不到匹配的 case,也就不会显示任何消息。
有件事值得注意,如果您使用 IF 语句而不是第 8 行的 SWITCH,则可能会显示一条消息。这取决于您如何实现 IF。这就是这里的目标:让你像程序员一样思考。我不会告诉你该怎么做,相反,我希望你像程序员一样思考,试着理解每条语句是如何工作的,包括它的功能和局限性。
最后的探讨
在本文中,我们探讨了 SWITCH 语句的工作原理。我明白,乍一看,它可能会造成更多的混乱,而不是解决问题。但我向你保证,在你练习之后,它会对你非常有用。我将再次强调这一点:慢慢研究每个控制结构。尝试以稍微不同的方式复制相同的行为。这将帮助你更清楚地了解一切是如何结合在一起的。
从小事做起,不要急于立即创建指标或 EA 交易。首先打下坚实的知识基础。这样,您的开发工作将更有效率,您的辛勤工作将得到更好的回报。所以,慢慢来,查看随附的代码,我们下一篇文章见。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/15391



