C程序循环语句

循环(loop)是重复执行其他语句(循环体)的一种语句。在C语言中,每个循环都有一
个控制表达式(controlling expression)。每次执行循环体(循环重复一次)时都
要对控制表达式求值。如果表达式为真(即值不为零),那么继续执行循环。

while语句


[while语句] while (表达式) 语句
圆括号内的表达式是控制表达式,圆括号后边的语句是循环体。
while (i < n) /* controlling expression */
i = i * 2; /* loop body */
注意,这里的圆括号是强制要求的,而且在右括号和循环体之间没有任何内容。
执行while语句时,首先计算控制表达式的值。如果值不为零(即真值),那么执行循环
体,接着再次判定表达式。这个过程(先判定控制表达式,再执行循环体)持续进行,直
到控制表达式的值变为零才停止。


下面的例子使用while语句计算大于或等于数n的最小的2的幂:
i = 1;
while (i < n)
i = i * 2;
假设n的值为10。下面的跟踪显示了while语句执行时的情况。
● i = 1;        i现在值为1。
● i < n成立吗?     是的,继续。
● i = i * 2;      i现在值为2。
● i < n成立吗?     是的,继续。
● i = i * 2;      i现在值为4。
● i < n成立吗?     是的,继续。
● i = i * 2;      i现在值为8。
● i < n成立吗?     是的,继续。
● i = i * 2;      i现在值为16。
● i < n成立吗?     不成立,退出循环。
注意,只有在控制表达式i < n为真的情况下循环才会继续。当表达式值为假时,循环终
止,而且就像描述的那样,此时i的值是大于或等于n的。
虽然循环体必须是单独的一条语句,但这只是个技术问题;如果需要多条语句,那么只要
用一对花括号构造成一条复合语句
while (i > 0) {
printf("T minus %d and counting\n", i);
i--;
}
即使在没有严格要求的时候,一些程序员也总是使用花括号:
while (i < n) { /* braces allowed, but not required */
i = i * 2;
}
再看一个跟踪语句执行的示例。下面的语句显示一串“倒计数”信息。
i = 10;
while (i > 0) {
printf("T minus %d and counting\n", i);
i--;
}
在while语句执行前,把变量i赋值为10。因为10大于0,所以执行循环体,这导致显示出
信息T minus 10 and counting,同时变量i进行自减。然后再次判定条件i > 0。因
为9大于0,所以再次执行循环体。整个过程持续,直到显示信息T minus 1 and
counting,并且变量i的值变为0时停止。然后判定条件i > 0的结果为假,导致循环终
止。
“倒计数”的例子可以引发对while语句的一些讨论。
● 在while循环终止时,控制表达式的值为假。因此,由表达式i > 0控制的循环终止
时,i一定是小于或等于0的。(否则还将执行循环!)
● 可能根本不执行while循环体。因为控制表达式在循环体执行之前进行判定,所以循
环体有可能一次也不执行。第一次进入倒计数循环时,如果变量i的值是负数或零,
那么不会执行循环。
● while语句通常可以有多种写法。例如,我们可以在printf函数调用的内部进行变
量i的自减操作,这样可以使倒计数循环更加简洁:
while (i > 0)
printf("T minus %d and counting\n", i--);
无限循环
如果控制表达式的值始终非零,while语句将无法终止。事实上,C程序员有时故意用非
零常量作为控制表达式来构造无限循环:
[惯用法] while (1)...
除非循环体中含有跳出循环控制的语句(break、goto、return)或者调用了导致程序
终止的函数,否则上述形式的while语句将永远执行下去。
如何把输出整齐地排成两列的。窍门是使用类似%10d这样的转换
说明代替%d,并利用了printf函数在指定宽度内输出右对齐的特性。
程序 数列求和
在下面这个用到while语句的示例中,我们编写了一个程序,对用户输入的整数数列进行
求和计算。下面显示的是用户能看到的内容:
This program sums a series of integers.
Enter integers (0 to terminate): 8 23 71 5 0
The sum is: 107
很明显,该程序需要一个循环来读入数(用scanf函数)并将其累加。
用n表示当前读入的数,而sum表示所有先前读入的数的总和,得到如下程序:
sum.c
/* Sums a series of numbers */
#include <stdio.h>
int main(void)
{
int n, sum = 0;
printf("This program sums a series of integers.\n");
printf("Enter integers (0 to terminate): ");
scanf("%d", &n);
while (n != 0) {
sum += n;
scanf("%d", &n);
}
printf("The sum is: %d\n", sum);
return 0;
}
注意,条件n != 0在数被读入后立即进行判断,这样可以尽快终止循环。此外,程序中
用到了两个完全一样的scanf函数调用,在使用while循环时往往很难避免这种现象。

do语句


do语句和while语句关系紧密。事实上,do语句本质上就是while语句,只不过其控制表
达式是在每次执行完循环体之后进行判定的。do语句的格式如下所示:
[do语句]  do 语句 while (表达式);
和处理while语句一样,do语句的循环体也必须是一条语句(当然可以用复合语句),并
且控制表达式的外面也必须有圆括号。
执行do语句时,先执行循环体,再计算控制表达式的值。如果表达式的值是非零的,那么
再次执行循环体,然后再次计算表达式的值。在循环体执行后,若控制表达式的值变为0,
则终止do语句的执行。
下面使用do语句重写前面的“倒计数”程序:
i = 10;
do {
printf("T minus %d and counting\n", i);
--i;
} while (i > 0);
执行do语句时,先执行循环体,这导致显示出信息T minus 10 and counting,并且
i自减。接着对条件i > 0进行判定。因为9大于0,所以再次执行循环体。这个过程持
续,直到显示出信息T minus 1 and counting并且i的值变为0。此时判定表达式i >
0的值为假,所以循环终止。正如此例中显示的一样,do语句和while语句往往难以区
别。两种语句的区别是,do语句的循环体至少要执行一次,而while语句在控制表达式初
始值为0时会完全跳过循环体。
顺便提一下,无论需要与否,最好给所有的do语句都加上花括号,这是因为没有花括号的
do语句很容易被误认为while语句:
do
printf("T minus %d and counting\n", i--);
while (i > 0);
粗心的读者可能会把单词while误认为while语句的开始。
程序 计算整数的位数
虽然C程序中while语句的出现次数远远多于do语句,但是后者对于至少需要执行一次的
循环来说是非常方便的。为了说明这一点,现在编写一个程序计算用户输入的整数的位
数:
Enter a nonnegative integer: 60
The number has 2 digit(s).
方法是把输入的整数反复除以10,直到结果变为0时停止,除法的次数就是所求的位数。因
为不知道到底需要多少次除法运算才能达到0,所以很明显程序需要某种循环。但是应该用
while语句还是do语句呢?do语句显然更合适,因为每个整数(包括0)都至少有一位数
字。下面是这个程序。
numdigit.c
/* Calculates the number of digits in an integer */
#include <stdio.h>
int main(void)
{
int digits = 0, n;
printf("Enter a nonnegative integer: ");
scanf("%d", &n);
do {
n /= 10;
digits++;
} while (n > 0);
 printf("The number has %d digit(s).\n", digits);
return 0;
}
为了说明do语句是正确的选择,下面观察一下如果用相似的while循环替换do循环会发生
什么:
while (n > 0) {
n /= 10;
digits++;
}
如果n初始值为0,上述循环根本不会执行,程序将打印出
The number has 0 digit(s).

for语句


现在开始介绍C语言循环中最后一种循环,也是功能最强大的一种循环:for语句。不要因
为for语句表面上的复杂性而灰心;实际上,它是编写许多循环的最佳方法。for语句非
常适合应用在使用“计数”变量的循环中,当然它也可以灵活地用于许多其他类型的循环
中。
for语句的格式如下所示:
[for语句格式]  for (声明或者表达式1; 表达式2; 表达式3) 语句
其中表达式1、表达式2和表达式3全都是表达式。下面是一个例子:
for (i = 10; i > 0; i--)
printf("T minus %d and counting\n",i);
在执行for语句时,变量i先初始化为10,接着判定i是否大于0。因为判定的结果为真,所
以打印信息T minus 10 and counting,然后变量i进行自减操作。随后再次对条件i
> 0进行判定。循环体总共执行10次,在这一过程中变量i从10变化到1。
for语句和while语句关系紧密。 事实上,除了一些极少数的情况以外,for循环总可
以用等价的while循环替换:
表达式1;
while (表达式2) {
语句
表达式3;
}
就像上面这个模式显示的那样,表达式1是循环开始执行前的初始化步骤,只执行一次;
表达式2用来控制循环的终止(只要表达式2的值不为零,循环就会持续执行);表达式3
是每次循环中最后被执行的一个操作。把这种模式用于先前的for循环示例中,可以得
到:
i = 10;
while (i > 0) {
printf("T minus %d and counting\n", i);
i--;
}
研究等价的while语句有助于更好地理解for语句。例如,假设把先前的for循环示例中
的i--替换为--i:
for (i = 10; i > 0; --i)
printf("T minus %d and counting\n", i);
这样做会对循环产生什么样的影响呢?看看等价的while循环就会发现,这种做法对循环
没有任何影响:
i = 10;
while (i > 0) {
printf("T minus %d and counting\n", i);
--i;
}
因为for语句中第一个表达式和第三个表达式都是以语句的方式执行的,所以它们的值互
不相关——它们有用仅仅是因为有副作用。结果,这两个表达式常常用作赋值表达式或自
增/自减表达式。
6.3.1 for语句的惯用法
对于“向上加”(变量自增)或“向下减”(变量自减)的循环来说,for语句通常是最
好的选择。对于向上加或向下减共n次的情况,for语句经常会采用下列形式中的一种。
● 从0向上加到n-1:
[惯用法] for ( i = 0; i < n; i++) ...
● 从1向上加到n:
[惯用法] for ( i = 1; i <= n; i++) ...
● 从n-1向下减到0:
[惯用法] for ( i = n - 1; i >= 0; i--) ...
● 从n向下减到1:
[惯用法] for ( i = n; i > 0; i--) ...
模仿这些书写格式有助于避免C语言初学者常犯的下列错误。
● 在控制表达式中把>写成<(或者相反)。注意,“向上加”的循环使用运算符<或运
算符<=,而“向下减”的循环则依赖于运算符>或运算符>=。
● 在控制表达式中把<、<=、>或>=写成==。控制表达式的值在循环开始时应该为真,
以后会变为假以便能终止循环。类似i == n这样的判定没什么意义,因为它的初始
值不为真。
● 编写的控制表达式中把i < n写成i <= n,这会犯“循环次数差一”错误。
在for语句中省略表达式
for语句远比目前看到的更加灵活。通常for语句用三个表达式控制循环,但是有一些for
循环可能不需要这么多,因此C语言允许省略任意或全部的表达式。
如果省略第一个表达式,那么在执行循环前没有初始化的操作:
i = 10;
for (; i > 0; --i)
printf("T minus %d and counting\n", i);
在这个例子中,变量i由一条单独的赋值语句实现了初始化,因此在for语句中省略了第一
个表达式。(注意,保留第一个表达式和第二个表达式之间的分号。即使省略掉某些表达
式,控制表达式也必须始终有两个分号。)
如果省略了for语句中的第三个表达式,循环体需要确保第二个表达式的值最终会变为
假。我们的for语句示例可以这样写:
for (i = 10; i > 0;)
printf("T minus %d and counting\n", i--);
为了补偿省略第三个表达式产生的后果,我们使变量i在循环体中进行自减。
当for语句同时省略掉第一个和第三个表达式时,它和while语句没有任何分别。例如,
循环
for (; i > 0;)
printf("T minus %d and counting\n", i--);
等价于
while (i > 0)
printf("T minus %d and counting\n", i--);
这里while语句的形式更清楚,因此也更可取。
如果省略第二个表达式,那么它默认为真值,因此for语句不会终止(除非以某种其他形
式停止)。 例如,某些程序员用下列for语句建立无限循环:
[惯用法] for ( ; ; )...


C99中的for语句


在C99中,for语句的第一个表达式可以替换为一个声明,这一特性使得程序员可以声明一
个用于循环的变量:
for (int i = 0; i < n; i++)

变量i不需要在该语句前进行声明。事实上,如果变量i在之前已经进行了声明,这个语句
将创建一个新的i且该值仅用于循环内。
for语句声明的变量不可以在循环外访问(在循环外不可见):
for (int i = 0; i < n; i++) {
...
printf("%d", i); /* legal; i is visible inside loop */
...
}
printf("%d", i); /*** WRONG ***/
让for语句声明自己的循环控制变量通常是一个好办法:这样很方便且程序的可读性更
强,但是如果在for循环退出之后还要使用该变量,则只能使用以前的for语句格式。
顺便提一下,for语句可以声明多个变量,只要它们的类型相同:
for (int i = 0, j = 0; i < n; i++)


 逗号运算符


编写有两个(或更多个)初始表达式的for语句,或者希望在每次循环时一次对几个变量进行自增操作。使用逗号表达式(comma expression)作为for语句中第一个或第三个表达式可以实现这些想法。
逗号表达式的格式如下所示:
[逗号表达式] 表达式1, 表达式2
这里的表达式1和表达式2是两个任意的表达式。逗号表达式的计算要通过两步来实现:第
一步,计算表达式1并且扔掉计算出的值;第二步,计算表达式2,把这个值作为整个表达
式的值。对表达式1的计算应该始终会有副作用;如果没有,那么表达式1就没有了存在的
意义。
例如,假设变量i和变量j的值分别为1和5,当计算逗号表达式++i, i+j时,变量i先进
行自增,然后计算i+j,所以表达式的值为7。(而且,显然现在变量i的值为2。)逗号运算符的优先级低于所有其他运算符,所以不需要在++i和i+j外面加圆括
号。
有时需要把一串逗号表达式串联在一起,就如同某些时候把赋值表达式串联在一起一样。
逗号运算符是左结合的,所以编译器把表达式
i = 1, j = 2, k = i + j
解释为
((i = 1), (j = 2)), (k = (i + j))
因为逗号表达式的左操作数在右操作数之前求值,所以赋值运算i = 1、j = 2和k = i
+ j是从左向右进行的。
提供逗号运算符是为了在C语言要求只能有一个表达式的情况下,可以使用两个或多个表
达式。逗号运算符允许将两个表达式“粘贴”在一起构成一个表达式。(注意它与复合语句的相似之处,后者允许我们把一组语句当作一条语句来使用。)
需要把多个表达式粘在一起的情况不是很多。正如后面的某一章将介绍的那样,某些宏定义可以从逗号运算符中受益。除此之外,for语句是唯一可以发现逗号运算符的地方。例如,假设在进入for语句时希望初始化两个变量。可以把原来的程序
sum = 0;
for (i =1; i <= N; i++)
sum += i;
改写为
for (sum = 0, i = 1; i <= N; i++)
sum += i;
表达式sum = 0, i = 1首先把0赋值给sum,然后把1赋值给i。利用附加的逗号运算
符,for语句可以初始化更多的变量。


退出循环


编写循环时在循环体之前(使用while语句和for语句)或之后(使用do语
句)设置退出点的方法。然而,有些时候也需要在循环中间设置退出点,甚至可能需要对
循环设置多个退出点。break语句可以用于有上述这些需求的循环中。
continue语句会跳过某次迭代的部分内容,但是不会跳出整个循环。goto语句允许程序
从一条语句跳转到另一条语句。因为已经有了break和continue这样有效的语句,所以
很少使用goto语句。


break语句


前面已经讨论过break语句把程序控制从switch语句中转移出来的方法。break语句还
可以用于跳出while、do或for循环。
假设要编写一个程序来测试数n是否为素数。我们的计划是编写一个for语句用n除以
2~n-1的所有数。一旦发现有约数就跳出循环,而不需要继续尝试下去。在循环终止后,
可以用一个if语句来确定循环是提前终止(因此n不是素数)还是正常终止(n是素数):
for (d = 2; d < n; d++)
if (n % d == 0)
break;
if (d < n)
printf("%d is divisible by %d\n", n, d);
else
printf("%d is prime\n", n);
对于退出点在循环体中间而不是循环体之前或之后的情况,break语句特别有用。读入用
户输入并且在遇到特殊输入值时终止的循环通常属于这种类型:
for (;;) {
printf("Enter a number (enter 0 to stop): ");
scanf("%d", &n);
if (n == 0)
break;
printf("%d cubed is %d\n", n, n * n * n);
}
break语句把程序控制从包含该语句的最内层while、do、for或switch语句中转移出
来。当这些语句出现嵌套时,break语句只能跳出一层嵌套。switch语句嵌在while语句中的情况:
while (...) {
switch (...) {
...
break;
...
}
}
break语句可以把程序控制从switch语句中转移出来,但是不能跳出while循环。
 

continue语句


continue语句无法跳出循环。break语句刚好把程序控制转移到循环体末尾之后,而continue语句刚好把程序控制转移到循环体末尾之前。break语句会使程序控制跳出循环,而continue语句会把程序控制留在循环内。break语句和continue语句的另外一个区别是,break语句可以用于switch语句和循环(while、do和for),而continue语句只能用于循环。
下面的例子通过读入一串数并求和的操作说明了continue语句的简单应用。循环在读入
10个非零数后终止。无论何时读入数0都执行continue语句,控制将跳过循环体的剩余部
分(即语句sum += i;和语句n++;)但仍留在循环内。
n = 0;
sum = 0;
while (n < 10) {
scanf("%d", &i);
if (i == 0)
continue;
sum += i;
n++;
/* continue jumps to here */
}
如果不用continue语句,上述示例可以写成如下形式:
n = 0;
sum = 0;
while (n < 10) {
scanf("%d", &i);
if (i != 0) {
sum += i;
n++;
}
}

goto语句


break语句和continue语句都是跳转语句:它们把控制从程序中的一个位置转移到另一
个位置。这两者都是受限制的:break语句的目标是包含该语句的循环结束之后的那一
点,而continue语句的目标是循环结束之前的那一点。goto语句则可以跳转到函数中任
何有标号的语句处。
标号只是放置在语句开始处的标识符:
[标号语句]  标识符 : 语句
一条语句可以有多个标号。goto语句自身的格式如下:
[goto语句]  goto 标识符;
执行语句goto L;,控制会转移到标号L后面的语句上,而且该语句必须和goto语句在同
一个函数中。
如果C语言没有break语句,可以用下面的goto语句提前退出循环:
for (d = 2; d < n; d++)
if (n % d == 0)
goto done;
done:
if (d < n)
printf("%d is divisible by %d\n", n, d);
else
printf("%d is prime\n", n);
goto语句在早期编程语言中很常见,但在日常C语言编程中已经很少用到它了。
break、continue、return语句(本质上都是受限制的goto语句)和exit函数(➤9.5
节)足以应付在其他编程语言中需要goto语句的大多数情况。
虽然如此,goto语句偶尔还是很有用的。考虑从包含switch语句的循环中退出的问题。
正如前面看到的那样,break语句不会产生期望的效果:它可以跳出switch语句,但是
无法跳出循环。goto语句解决了这个问题:
while (...) {
switch (...) {
...
goto loop_done; /* break won't work here */
...
}
}
loop_done: ...
goto语句对于嵌套循环的退出也是很有用的。
 

  • 9
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值