for 循环结构
此循环结构由带有 for 关键字的语句实现,因此而得名。概括地说,它可以描述如下:
for ( [initialization] ; [condition] ; [expression] )
|
在标题中,在单词 for 之后,以下内容用圆括号括起:
- 初始化:在循环结构开始之前进行一次性初始化的语句;
- 条件:在每次迭代开始时检查的布尔条件,只要为真,循环就运行;
- 表达式:当循环体中的所有语句都已通过时,在每次迭代结束时执行的计算公式。
循环体可以是简单语句,也可以是复合语句。
头部的三个组成部分都是可选的,可按任何组合省略,也可以全部省略。
初始化可能包括变量的声明(以及初始值的设置)或给已存在的变量赋值。这种变量称为循环变量。如果它们是在头部中声明的,那么它们的作用域和生存期受循环结构的限制。
如果初始化后条件为 true,则循环开始执行,并在每次后续迭代开始时只要条件为真就继续执行。如果在下一次检查期间发现违反了条件,则循环退出,即控制权被转移给在循环及循环体之后编写的语句。如果在循环开始前(初始化后)条件为 false,则循环永远不会被执行。
条件和表达式通常包括循环变量。
执行循环意味着执行循环体。
for 循环结构最常见的形式是,使用单个循环变量来控制迭代次数。在下面的例子中,我们计算 a 数组中各数的平方。
int a[] = {1, 2, 3, 4, 5, 6, 7};
|
该循环按以下步骤执行:
- 创建初始值为 0 的变量 i。
- 检查条件:变量 i 是否小于 n 循环的大小。只要为真,循环就会继续。如果为假,就会跳转到调用 ArrayPrint 函数的语句。
- 如果条件为真,则执行循环体的语句。在本例中,数组的第 i 个元素获得该元素初始值与其本身的乘积,即每个元素的值被它的平方值代替。
- 变量 i 递增 1。
然后从第 2 步开始重复所有步骤。退出循环后,它的变量 i 被销毁,试图访问该变量将导致错误。
第 4 步的表达式可以具有任意复杂度,而不仅仅是递增循环变量。例如,要迭代偶数或奇数元素,可以编写 i += 2。
不管循环体由多少条语句组成,都建议将它写到与头部隔开的独立代码行中。这有利于逐步调试过程。
初始化可以包括多个变量声明,但它们必须是同一类型,因为它们属于一条语句。例如,要以相反的顺序重新排列元素,可以编写这样一个循环(这只是为了演示循环,实际上有一个内置函数 ArrayReverse 可以反转数组中的顺序,参见 拷贝和编辑数组):
for(int i = 0, j = n - 1; i < n / 2; ++i, --j)
|
辅助变量 temp 在每条循环路径中被创建和删除,但是编译器只在进入函数时为这个变量分配一次内存,就像所有局部变量一样。此优化对于内置类型效果很好。然而,如果在循环中描述了 自定义类对象 ,那么每次迭代时都会调用其构造函数和析构函数。
在循环体中改变循环变量是可以接受的,但是这种技巧只在非常特殊的情况下使用。一般不建议这样做,因为这可能会导致错误(特别是,处理过的元素可能会被跳过,或者执行可能会进入无限循环)。
为了演示可以省略头部构成,我们来设想以下问题:我们需要找出同一个数组中元素的个数,个数总和小小于 100。为此,我们需要在循环的前面定义一个计数器变量 k ,因为这个变量在循环完成后必须继续存在。我们还将创建 sum 变量来累加计算总和。
int k = 0, sum = 0;
|
因此,不需要在头部进行初始化。此外,在计算总和的表达式中直接使用后缀递增,将 k 计数器递增(访问数组元素时)。因此,标题中不需要表达式。
在循环结束时,我们打印出 k 以及总和减去最后添加元素,因为正是这个元素导致超过了上限 100。
请注意,即使循环体中只有一条语句,我们也在使用复合代码块。这很有用,因为当程序增长时,在括号内添加附加语句的所有操作都已经完成。此外,这种方法保证了所有循环风格统一。但是最终的选择取决于程序员。
在明确、极简版本中,循环头部可能如下所示:
for( ; ; )
|
如果在这样的循环中没有因某些条件而中断循环的语句,它将被无限执行。我们将分别在 Break 跳转语句 和 If 选择语句 中学习如何中断循环和测试条件。
这种循环算法通常在服务中使用(它们被设计用于持续的后台工作),以监控终端或外部网络资源的状态。它们通常包含按指定间隔暂停程序的语句,例如,使用内置函数 Sleep。如果没有这种预防措施,无限循环将导致一个处理器内核的负载达到 100%。
StmtLoopsFor.mq5 脚本在结尾包含一个无限循环,但这只是出于演示目的。
for( ; ; )
|
在循环中,计算机的内部计时器 (GetTickCount) 使用 Comment 函数每秒显示一次:值显示在图表左上角。只有用户可以通过从图表中删除整个脚本来中断循环(“专家”对话框中的“删除”按钮)。这段代码不会检查循环内部是否有此类用户停止请求,但有一个内置函数 IsStopped 可以实现此目的。如果用户已发出了停止命令,它将返回 true。尤其是在程序中包含循环和长期计算的情况下,最好能检查该函数的值,并在收到 true 时主动终止循环和整个程序。否则,在本例中将发生这样的情况,终端将在等待 3 秒后强制终止脚本(向日志中输出“异常终止”)。
此循环结构的更优版本应该是:
for( ; !IsStopped(); ) // continue until user interrupt
|
但是,使用另一个重复语句 while 可以更好地实现这个循环。根据经验,只有当有明显的循环变量和/或预先确定的迭代次数时,才应该使用 for 循环结构。在这种情况下,不满足这些条件。
循环变量通常是整数 - 尽管也允许其他类型,如 double。这是因为循环操作的内在逻辑隐含了迭代的编号。此外,总是可以从整数索引中计算出所需的实数,而且精度更高。例如,以下循环以 0.01 为增量在 0.0 到 1.0 的值之间迭代:
for(double x = 0.0; x < 1.0; x += 0.01) { ... } |
可以用一个具有整数变量的类似循环来代替:
for(int i = 0; i < 100; ++i) { double x = i * 0.01; ... } |
在第一种情况下,当加上 x += 0.01 时,浮点计算的误差逐渐累积。在第二种情况下,每个 x 值在一次 i * 0.01 运算中获得,具有最大可用精度。
通常给循环变量指定以下单字母名称,例如 i, j, k, m, p, q。当循环彼此嵌套或者在同一个循环内部同时计算前向(递增)和后向(递减)索引时,需要多个名称。
顺便说一下,这是一个嵌套循环的例子。下面的代码计算乘法表并将其存储在二维数组中。
int table[10][10] = {0};
|