
从基础到中级:FOR 语句
概述
此处提供的内容仅用于教育目的。在任何情况下,除了学习和掌握所提出的概念外,都不应出于任何目的使用此应用程序。
在上一篇文章,从基础到中级:SWITCH 语句中,我解释并演示了如何以最基本和最简单的形式使用 SWITCH 运算符。努力理解到目前为止所涵盖的每个语句的工作原理非常重要。不是以一种鼓励死记硬背的方式,而是以一种让你思考如何应用到目前为止提出的每个语句和概念的方式。
我理解你们中的一些人可能会觉得这些材料过于简单或缺乏深度。毕竟,我们可以探索更高级的主题。然而,基于我多年的编程经验,我逐渐意识到,当基本概念被很好地理解时,即使乍一看,更复杂和复杂的主题也会变得更容易掌握。这正是我通过这些文章所要达到的目的。亲爱的读者,我希望您能理解每个元素在最基本和最根本的层面上是如何运作的。因此,当我们最终深入研究更先进的材料时,你将能够更快、更轻松地理解它。
非常好,在 MQL5 中可用于控制应用程序执行流程的所有语句中,只剩下一个需要介绍。这是 MQL5 中为此目的而设计的最后一个操作符。是的,我指的是 FOR。然而,正如你现在可能已经注意到的,我更喜欢单独介绍主题。这种方法使理解解释变得更加容易。所以说,让我们开始一个新的主题。
FOR 语句的前提
尽管许多初学者倾向于认为有大量的语句需要学习,但大多数编程语言实际上只依赖于少数语句。至少在控制流命令方面是这样。不要将这些与库调用或保留关键字混淆;它们完全不同。控制流语句的数量通常非常有限。另一方面,即使在一种编程语言中,库调用、保留关键字以及辅助函数和过程也可以很容易地达到数千个。以 C/C++ 为例,控制流命令的数量很少。然而,即使不计算库调用,可用的辅助函数和过程的数量也是如此之多,以至于任何人都不可能掌握整个语言。实际情况是,程序员专门从事某个特定领域。但是,由于他们对语言有扎实的基础理解,只要有足够的时间使他们的知识适应手头的任务,他们就能够解决任何问题。
这就是为什么即使是经验丰富的程序员也在不断学习。他们从未停止学习或寻求跟上新兴趋势。这与许多人开始学习编程时的假设大不相同。许多人认为,仅仅知道一些事情就已经让他们成为程序员。但事实上,事情并不是这样的,必须不断研究和改进旧技术。只有这样,你才能达到值得宣布的卓越水平:我是一名程序员。
那么,我为什么要说这些呢?原因很简单,亲爱的读者。到目前为止,我们已经介绍了所有内容,如果你花时间学习并加深对已经介绍的内容的理解,你将能够应对几乎任何编程挑战。这是因为 FOR 语句本质上是一个循环构造。但是我们已经探索了使用 WHILE 和 DO WHILE 的循环。为什么我们还需要另一个循环语句呢?这似乎没有什么意义,不是吗?
好吧,事实上,FOR 语句与 WHILE 和 DO WHILE 同时存在是有充分理由的。原因很简单:在某些情况下,使用 WHILE 或 DO WHILE 并不理想。我知道这听起来可能有点奇怪,但是的,在某些情况下,使用 FOR 比 WHILE 或 DO WHILE 更合适。其中许多情况与循环中是否使用 CONTINUE 语句有关。
然而,理解这一点非常重要:FOR 语句是迄今为止程序员最喜欢的语句。为什么呢?因为用它编写的代码比使用 WHILE 或 DO WHILE 编写的相同循环更清晰、更易读。这是为什么呢?原因是 FOR 语句通常(这很关键)在其声明中包含与循环控制相关的所有元素。相比之下,使用 WHILE 和 DO WHILE,控制逻辑往往分散在循环体内的某个地方。这种简单的区别使程序员更喜欢 FOR,因为只需查看 FOR 声明本身,就可以更容易地修复、修改甚至理解循环,而不必深入循环来找到控制逻辑。
但是等一下,如果 FOR 语句更简单、更适合控制循环,为什么我们现在才讨论它?早点介绍它不是更有意义吗?亲爱的读者,诚然,这是一个有点争议的问题。虽然 FOR 语句乍一看确实更简单,但它具有某些特征,可能会使其变得相当复杂。这就是为什么我选择首先介绍 WHILE 和 DO WHILE,而将 FOR 作为最后介绍的控制语句。
尽管我们首先从最基本、最简单的 FOR 形式开始研究,但对于我们双方来说(假设您已经研究过前面的语句),理解 FOR 的工作原理会容易得多。也许我应该进一步推迟这个解释,因为我们还没有讨论对整个编程有重大影响的运算符优先级规则。尽管如此,即使没有探索这些规则,我们仍然可以使用控制流语句做很多事情。这使我能够使用这些文章和 MQL5 文档作为基础,创建适合真正刚刚开始编程之旅的读者的内容。那将是理想的情况。但由于我知道许多人已经接触过编程,尽管不一定有深入的理解,我的目标是取得平衡,让每个人都达到一个共同的水平,以便我们稍后探索。
很好,现在是时候看看 FOR 语句的实际作用了。但让我们继续一个新的话题,因为我希望这次第一次见面对每个人来说都是顺利和愉快的。
FOR 语句
引入新命令的最简单方法是使用我们已经看到的东西。所以我们要做的是:首先,我们将重用本文中讨论 WHILE 语句和 DO WHILE 对的脚本。这将使过渡更加平稳。考虑一下,第一次接触更像是将之前的代码翻译成新的形式 — 这次只使用FOR语句。
让我们从最简单的代码开始:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. char info = 10; 07. 08. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 09. 10. while (info) 11. { 12. info = info - 1; 13. Print(__FUNCTION__, " : ", info); 14. } 15. 16. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 17. } 18. //+------------------------------------------------------------------+
代码 01
由于这是所有示例中最简单的一个,在我们研究如何使用 FOR 语句重写这段代码之前,了解变量生命周期的概念以及全局变量和局部变量之间的区别至关重要。这些概念在本系列的早期文章中已经介绍过。因此,如果你还不熟悉全局变量和局部变量之间的区别,我建议你重新阅读第一篇文章,以便更好地理解这里将要解释的内容。从从基础到中级:变量(一)开始 。
假设您已经具备必要的背景知识,现在让我们翻译代码 01 以使用 FOR 语句。有多种方法可以做到这一点,每种方法都会在代码中引入或更改某些内容。我们将从与代码 01 非常相似的版本开始。如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. char info = 10; 07. 08. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 09. 10. for( ; info; ) 11. { 12. info = info - 1; 13. Print(__FUNCTION__, " : ", info); 14. } 15. 16. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 17. } 18. //+------------------------------------------------------------------+
代码 02
此代码 02 说明了使用 FOR 语句的第一种方法。请注意,我们只更改了第 10 行。但是,代码 01 和代码 02 的结果仍然相同。您可以在下图中看到此结果:
图 01
现在,看看代码 02,您可能会感到有点困惑。这是因为,如果你以前遇到过其他 FOR 语句,那么第十行显示的语句可能与你在大多数示例中看到的完全不同。这正是为什么亲爱的读者,真正吸收前几篇文章中介绍的知识对你来说至关重要。FOR 并不是一个普通的运算符。它实际上由三个部分组成,每个部分都有一个必须很好理解的特定目的。在我们继续讨论细节之前,让我们探索重写同一代码 01 的第二种选择。这种替代方案可以在下面找到:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. char info = 10; 07. 08. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 09. 10. for( ; info; info = info - 1) 11. Print(__FUNCTION__, " : ", info); 12. 13. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 14. } 15. //+------------------------------------------------------------------+
代码 03
现在,亲爱的读者,请考虑以下内容。您在代码 03 中看到的内容与以前不同。这是因为变量 “info” 的值不会按照您期望的方式被修改。是的,会修改,但是结果会不一样。因此,当您运行代码 03 时,您实际会得到如下所示的结果:
图 02
但为什么结果却不同呢?这可能看起来有点奇怪。在这种情况下,我们不应该使用 FOR 运算符来创建循环吗?好吧,亲爱的读者,让我们一步一步来。首先,可以使用 FOR 循环以与图 01 所示结果匹配的方式创建此迭代。然而(这也是许多人开始感到困惑的地方),了解变量 “info” 的作用很重要。因为一切都取决于它,而不一定取决于 FOR 循环本身。我为什么这么说呢?因为根据具体情况,我们可能需要使用一种或另一种方法,以便代码 03 中的 FOR 语句产生与代码 02 中相同的结果。因此,你不应该试图记住如何使用该语句。相反,你应该专注于理解它是如何工作的。这就是事情经常变得混乱的地方。
这就是许多人放弃学习如何正确使用 FOR 语句的地方。但我会尽我所能以最简单的方式解释它。假设变量 'info' 确实需要在第六行声明。目前,原因并不重要;我们只是想在那一点上定义它。其次,我们希望 'info' 被循环修改,这样它的最终值就是导致循环终止的值。在这个阶段,有一个小问题,但我们还不会担心。让我们暂时坚持我们的假设。如果在代码 01 经过循环后检查其 “info” 变量的值,您会发现它的值为 0。同样的情况也发生在代码 02 中。但是在代码 03 中,该值为 1。但是正如您在图 02 中看到的,显示的第一个值是 10,而不是您所期望的 9。
那么你可能会想:如果我们将第六行声明的值从 10 改为 9 会怎样?那会发生什么?好吧,亲爱的读者,我把这留作一个小练习,供您自己探索。但这里有一些重要的事情要记住:通过改变第六行的值,依赖于 “info” 变量的一些计算或条件可能会受到影响。这就是为什么在不了解实际情况的情况下修改代码中的内容不是一个好主意。这里的目标不是随机调整代码的任何部分,而是专门关注第 10 行中正在做的事情。
好吧,我们可以简单地坚持使用代码 02 并按照其工作原理完成它。或者甚至回到代码 01,它也有效。但是,也许还有另一种方法可以使用 FOR 循环来解决我们的问题吗?其实,办法是有的。然而,它比我打算在本系列文章的这一点上介绍的要先进一些。不过,我可以向你展示另一种方法。这至少会使结果更接近我们的预期。即更接近图 01 所示。
因此,考虑到我们到目前为止讨论的所有假设,我们可以编写一个类似于下面所示的代码版本。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. char info = 10; 07. 08. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 09. 10. for(info = info - 1; info; info = info - 1) 11. Print(__FUNCTION__, " : ", info); 12. 13. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 14. } 15. //+------------------------------------------------------------------+
代码 04
此代码 04 仍然属于我认为的基本材料。虽然我们开始接触其他概念,但这仍然是使用 FOR 循环的基本方法。这个想法是为了确保我们即将看到的流程图有意义。运行此代码时,您将看到以下结果:
图 03
等一下,我不明白。现在计数从 9 开始,就像图 01 中一样。然而,计数会停在 1,就像图 02 中一样。为什么呢?循环运行后,变量 “info” 的值是否与其他代码(尤其是代码 01)中的值不同?不,亲爱的读者。经过循环后,“info” 变量的值在所有这些代码中都是 0,并且继续为 0。这里的问题是其他的事情。我们打印的不是 “info” 变量的实际最终值。相反,我们在 FOR 循环的特定迭代期间打印该值。这听起来可能有点令人困惑,但事实确实如此。为了真正理解这一点,我们需要查看代码的一个稍微不同的版本。但在我们这样做之前,让我们仔细看看 FOR 语句中的执行流。为此,让我们转向一个新的话题。
执行流程
乍一看,FOR 循环的执行流程可能比我们研究过的其他循环更令人困惑。然而,一旦你理解了它,就更容易理解为什么我们在上一个主题中看到了如此不同的输出。您可以看到下面的流程图:
图 04
在这里,我在箭头上使用了不同的颜色来帮助详细解释 FOR 循环的工作原理。您可能会惊讶地看到流程图中有一个 IF 语句。但在这里加入 IF 可以更容易地解释发生了什么。
好的,让我们一步一步来。第一部分是保留字 “for”,它标志着循环体的开始。紧接着,我们有了表达式 01。这可以是很多事情。但请注意,它只在循环开始时执行一次。通常,我们使用表达式 01 来初始化循环内的变量。但我们也可以使用它来调整全局值,就像我们在代码 04 中所做的那样。值得指出的是,表达式 01 也可以留空,就像我们在代码 02 和 03 中看到的那样。在这种情况下,编译器只是跳过它,什么也不做:没有初始化,没有声明。在这里声明变量时有一些具体的规则,但现在我们将保持基本的东西。因此,我们要么使用全局变量,要么将表达式留空。
执行表达式 01 后,我们继续下一步,如流程图中的 IF 所示。此时,检查表达式 02。严格来说,将其表示为 WHILE 而不是 IF 会更准确。但由于 WHILE 本身是一个循环语句,因此在解释 FOR 的图表中显示它可能会造成混淆。无论如何,如果表达式 02 的计算结果为 false,则循环终止。如果结果为 true,则执行循环内的代码块。这将是代码 03 和 04 中的第 11 行,或代码 02 中的第 11 行至第 14 行之间的代码块。循环体运行完毕后,我们转到表达式 03。这部分在许多情况下都很有趣,我们很快就会看到。根据循环的结构和它所服务的目标,我们可以创建这个结构的许多略有不同的版本。使表达式 03 特别有价值的是,如果使用它,它会使 FOR 循环比其他类型的循环更安全。这是因为您不会有忘记更新表达式 02 中检查的变量的风险。
说实话,这个流程图在实践中比图中看起来要容易理解得多。但如果你仔细看,你会注意到我用了一些绿色的箭头。这些绿色箭头表示循环如何流动。另一方面,红色箭头显示循环如何结束。
因此,如果你仔细观察并花点时间分析,你会注意到变量 info 在经过循环后最终为 0,这实际上是有道理的。但我们在终端上看到的打印值(查看代码 03)始终是 info + 1 也是有道理的。在所有其他版本的代码中,打印结果与我们最初预期的完全不同。
好的,我想我开始了解这个 FOR 循环了。它确实看起来很容易使用,尽管有时可能会有点令人困惑。但是我们之间:我们之前提到过,流程图中的 IF 实际上应该是 WHILE。我们怎样才能实现这一目标?我来解释一下。这一点 — 至少在我看来 — 相对容易理解,甚至不需要了解更高级的 FOR 循环细节。是的,还有更复杂的方面,尤其是对初学者来说。但是如果你想明白为什么我说流程图中的 IF 实际上应该是 WHILE,只需比较代码 01 和代码 02。由于表达式 01 和 03 为空,因此唯一真正起作用的是循环条件,其工作方式基本上与 WHILE 循环的工作方式相同。这就是为什么我提到流程图中的这个 IF 实际上应该代表 WHILE。
亲爱的读者,我不知道你是否注意到了这一点,但我正在尽我所能,暂时不要深入探讨更高级的 FOR 循环主题。这是因为我不希望你与 FOR 循环的第一次接触变得令人沮丧。不过,我不希望您认为 FOR 循环是不必要的,或者它总是可以用 WHILE 或 DO WHILE 代替。为了证明这一点,我将向您展示一种情况,在这种情况下,您不能随意更换。但我们将在下一节中介绍这一点。
空的 FOR 循环
我们即将看到的东西应该更多地被视为一种好奇心,而不是你在实践中应该使用的东西。老实说,我在这里要展示的内容很少在实际代码中使用。我做程序员已经很多年了,我可以用一只手数出我使用这种技术的次数。尽管如此,总有一天你可能会偶然发现一个非常不寻常的 FOR 循环 — 一个看起来很奇怪但仍然有效的循环。因为我们可能在一段时间内不会再讨论这个问题,而且你可能会比预期更早地遇到它,所以我现在需要向你展示。否则,我可能会忘记。为了解释我在说什么,让我们从一个非常简单的代码片段开始。我们会格外小心,以免遇到问题。因此,当我们讨论 WHILE 循环时,我们使用了如下代码:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. char info = 10; 07. 08. do 09. { 10. if (((info / 5) * 5) == info) Print("Press ESC to finish counting..."); 11. info = info - 1; 12. Sleep(200); 13. Print(__FUNCTION__, " : ", info); 14. }while (!TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)); 15. 16. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 17. } 18. //+------------------------------------------------------------------+
代码 05
这段代码的目的是创建一种具有明确出口的无限循环:使用 ESC 键。好的,但是,也可以使用完全空的 FOR 循环来实现同样的目的 — 创建无限循环。为了证明这一点,我们将使用下面显示的代码:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. char info = 10; 07. 08. for (; ; ) 09. { 10. if (((info / 5) * 5) == info) Print("Press ESC to finish counting..."); 11. info = info - 1; 12. Sleep(200); 13. Print(__FUNCTION__, " : ", info); 14. if (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) break; 15. } 16. 17. Print(__FUNCTION__, " ", __LINE__, " It will be executed anyway..."); 18. } 19. //+------------------------------------------------------------------+
代码 06
现在请密切关注,因为我不会重复这一点。回顾本文前面的示例,您会注意到它们都在所谓的表达式 02 中包含了一些条件。这使得 FOR 循环最终终止成为可能。但是,在某些情况下,您可能需要 FOR 循环不终止,至少不由于循环头本身内的条件而终止。在这种情况下,您可以将表达式 02 留空。当发生这种情况时,循环将不会终止,至少不会因为循环结构中声明的任何条件而终止。如果条件确实存在,它将被放入循环的例程中。如代码 06 所示,退出循环的条件定义在第 14 行。与许多人可能认为的相反(FOR 循环必须包含某种形式的表达式),事实并非如此。FOR 循环可以完全按照代码 06 第 8 行所示编写。当你这样做时,编译器会将其解释为无限循环。任何有经验的程序员都会理解这一点。
这就是为什么,当我们谈论使用 WHILE 语句的循环时,我提到循环是需要小心处理的。如果你不小心,你的代码可能会出现严重的问题。因此,在创建循环时始终要注意。
为了证明代码 06 是有效的,并且它不会陷入不受控制的无限循环,运行它的结果可以在下面的动画中看到:
动画 01
尽管如此(为了清楚起见,我这么说),我提出这个问题是为了,如果你遇到一段带有空 FOR 循环的代码,你就不会恐慌或想知道发生了什么。因为现在,你应该了解会发生什么,以及循环最终将如何终止 — 如果它终止的话。
最后的探讨
在本文中,我们介绍了 FOR 循环的基础知识。你在这里看到的一切都必须被彻底理解。与其他命令不同,FOR 循环包含一些特性,这些特性可能会使它变得非常复杂。所以不要让这样的事情堆积起来,开始学习和练习您在这里学到的知识。为此,请重新查看前面文章中的示例,并尝试对其进行调整,在适用的情况下用 FOR 循环替换现有循环。
由于这是我们必须涵盖的最后一个基本控制结构,从这一点开始,一切都将建立在我们迄今为止探索的基本概念之上。也就是说,我们还没有准备好继续讨论中间主题,因为还有几个基本概念需要讨论 — 每个概念都和你到目前为止学到的一样重要。然而,我现在更放心地介绍稍微更先进的编码技术。这将使我们能够更有效地介绍剩余的基本主题,而不会受到以前的限制。
所以,仔细研究我们已经涵盖的所有材料。我们下篇文章再见。祝你学业顺利!
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/15406


