6.1 概述
6.2 goto语句以及用 goto语句构成循环
6.3 while语句
6.4 do while语句
6.5 for 语句
6.6 循环的嵌套
6.7 几种循环的比较
6.8 break语句和 continue语句
6.9 程序举例习题第 6章 循 环 控 制
6.1 概述在许多问题中需要用到循环控制。例如,要输入全校学生成绩;求若干个数之和;迭代求根等。几乎所有实用的程序都包含循环。循环结构是结构化程序设计的基本结构之一,它和顺序结构、选择结构共同作为各种复杂程序的基本构造单元。
因此熟练掌握选择结构和循环结构的概念及使用是程序设计的最基本的要求。
(1) 用 goto语句和 if语句构成循环;
(2) 用 while语句;
(3) 用 do\|while语句;
(4) 用 for语句。
在下面各节中将分别作介绍。
6.2 goto语句以及用 goto语句构成循环
goto语句为无条件转向语句,它的一般形式为
goto语句标号;
语句标号用标识符表示,它的定名规则与变量名相同,即由字母、数字和下划线组成,其第一个字符必须为字母或下划线。不能用整数来做标号。例如:
goto label-1;
是合法的,而
goto 123;
是不合法的。结构化程序设计方法主张限制使用 goto
语句,因为滥用 goto语句将使程序流程无规律、
可读性差。但也不是绝对禁止使用 goto语句。一般来说,可以有两种用途:
(1) 与 if语句一起构成循环结构;
(2) 从循环体中跳转到循环体外,但在 c语言中可以用 break语句和 continue语句 (见 6.8节 )跳出本层循环和结束本次循环。 goto语句的使用机会已大大减少,只是需要从多层循环的内层循环跳到外层循环外时才用到 goto语句。但是这种用法不符合结构化原则,一般不宜采用,只有在不得已时 (例如能大大提高效率 )才使用。
例 6.1用 if语句和 goto语句构成循环,求 ∑n。
此问题的算法是比较简单的,可以直接写出程序:
main( )
{
int i,sum=0;
i=1;
loop,if(i<=100)
{ sum=sum+i;
i++;
goto loop;}
printf("%d",sum);
}
100
n=1
运行结果如下:
5050
这里用的是“当型”循环结构,当满足,i<=100” 时执行花括弧内的循环体。请读者自己画出流程图。
6.3 while语句
while语句用来实现“当型”循环结构。其一般形式如下:
while (表达式 ) 语句当表达式为非 0值时,执行 while语句中的内嵌语句。
其流程图见图 6.1。其特点是:先判断表达式,后执行语句。
图 6.1 图 6.2
例 6.2求 ∑n。用传统流程图和 N S结构流程图表示算法,见图 6.2(a)和图 6.2(b)。
根据流程图写出程序:
main()
{
int i,sum=0;
i=1;
while (i<=100)
{
sum=sum+i;
i++;
}
printf("%d",sum);
}
100
n=1
需要注意:
(1) 循环体如果包含一个以上的语句,应该用花括弧括起来,以复合语句形式出现。如果不加花括弧,则 while语句的范围只到 while后面第一个分号处。例如,本例中 while语句中如无花括弧,则
while语句范围只到,sum=sum+i;”。
(2) 在循环体中应有使循环趋向于结束的语句。例如,在本例中循环结束的条件是,i>100”,因此在循环体中应该有使 i增值以最终导致 i>100的语句,
今用,i++;”语句来达到此目的。如果无此语句,
则 i的值始终不改变,循环永不结束。
6.4 do while语句
do while语句的特点是先执行循环体,然后判断循环条件是否成立。其一般形式为
do
循环体语句
while (表达式 );
它是这样执行的:先执行一次指定的循环体语句,
然后判别表达式,当表达式的值为非零 (“真” ) 时,
返回重新执行循环体语句,如此反复,直到表达式的值等于 0为止,此时循环结束。可以用图 6.3表示其流程。请注意 do while循环用 N S流程图的表示形式 (图 6.3(b))。
图 6.3
例 6.3用 do while语句求 ∑n。
先画出流程图,见图 6.4。
图 6.4 图 6.5
100
n=1
程序如下:
main()
{
int i,sum=0;
i=1;
do
{sum=sum+i;
i++;
}
while(
i<=100);
printf("%d",sum);
}
可以看到:对同一个问题可以用 while语句处理,也可以用 do-while语句处理。 Do-while语句结构可以转换成 while结构。图 6.3可以改画成图 6.5形式,二者完全等价。而图 6.5中线框部分就是一个 while结构。可见,do-while结构是由一个语句加一个 while
结构构成的。若图 6.1中表达式值为真,则图 6.1也与图 6.5等价 (因为都要先执行一次语句 )。
在一般情况下,用 while语句和用 do-while语句处理同一问题时,若二者的循环体部分是一样的,它们的结果也一样。如例 6.2和例 6.3程序中的循环体是相同的,得到结果也相同。但是如果 while后面的表达式一开始就为假 (0值 )时,两种循环的结果是不同的。
例 6.4while和 do-while循环的比较。
(1) main ( ) (2) main( )
{int sum=0,i; {int sum=0,i;
scanf(“%d”,&i); scanf("%d",&i);
while (i<=10) do
{sum=sum+i; {sum=sum+i;
i++; i++;
} } while (i<=10)
printf(“sum=%d”,sum); printf(“sum=%d”,sum);
} }
运行情况如下:运行情况如下:
1 1
sum=55 sum=55
再运行一次,再运行一次:
11 11
sum=0 sum=11;
可以看到:当输入 i的值小于或等于 10时,二者得到结果相同。
而当 i>10时,二者结果就不同了。这是因为此时对 while循环来说,一次也不执行循环体 (表达式,i<=10”为假 ),而对
do while循环语句来说则要执行一次循环体。可以得到结论:当 while后面的表达式的第一次的值为“真”时,两种循环得到的结果相同。否则,二者结果不相同 (指二者具有相同的循环体的情况 )。
可以看到:当输入 i的值小于或等于 10时,二者得到结果相同。而当 i>10时,二者结果就不同了。这是因为此时对 while循环来说,一次也不执行循环体
(表达式,i<=10”为假 ),而对 do while循环语句来说则要执行一次循环体。可以得到结论:当 while
后面的表达式的第一次的值为“真”时,两种循环得到的结果相同。否则,二者结果不相同 (指二者具有相同的循环体的情况 )。
Do-while循环是先执行循环体,后判断表达式的
“当型”循环 (因为当条件满足时才执行循环体 )。
但利用它可以方便地实现如第 3章图 3.5所示的典型的“直到型”循环结构。典型的直到型 (until型 )循环结构是表达式为真时结束循环。因此在将图
6.4(a)
的算法改用图 6.4(b)画成用 N\|S结构流程图形式表示的直到型循环结构时,应将条件取“反”,即将图 6.4(a)中的,i≤100”改为图 6.4(b)中的,i>100”。
因为“当 i≤100时继续执行循环”和“直到 i>100结束循环”是对同一问题的两种表述方式。千万不要在图 5\|4(b)中写成“直到 i≤100”。
6.5 for 语 句
C语言中的 for语句使用最为灵活,不仅可以用于循环次数已经确定的情况,而且可以用于循环次数不确定而只给出循环结束条件的情况,它完全可以代替 while语句。
for语句的一般形式为
for(表达式 1;表达式 2;表达式 3) 语句它的执行过程如下:
(1) 先求解表达式 1。
(2) 求解表达式 2,若其值为真 (值为非 0),则执行
for语句中指定的内嵌语句,然后执行下面第 (3)步。
若为假 (值为 0),则结束循环,转到第 (5)步。
(3) 求解表达式 3。
(4) 转回上面第 (2)步骤继续执行。
(5) 循环结束,执行 for语句下面的一个语句。
可以用图 6.6来表示 for语句的执行过程。
for语句最简单的应用形式也就是最易理解的如下形式:
图 6.6
for(循环变量赋初值;循环条件;循环变量增值 ) 语句例如,
for(i=1;i<=100;i++) sum=sum+i;
它的执行过程与图 6.2完全一样。可以看到它相当于以下语句,i=1;
while(i<=100)
{sum=sum+i;
i++;
}
显然,用 for语句简单、方便。对于以上 for语句的一般形式也可以改写为 while循环的形式:
表达式 1;
while(表达式 2)
{语句表达式 3;
}
说明:
(1) for语句的一般形式中的“表达式 1”可以省略,此时应在
for语句之前给循环变量赋初值。注意省略表达式 1时,其后的分号不能省略。如 for(; i<=100;i++) sum=sum+i;执行时,跳过“求解表达式 1”这一步,其他不变。
(2) 如果表达式 2省略,即不判断循环条件,循环无终止地进行下去。也就是认为表达式 2始终为真。见图 6.7。
图 6.7
例如:
for(i=1; ;i++) sum=sum+i;
表达式 1是一个赋值表达式,表达式 2空缺。它相当于:
i=1;
while(1)
{sum=sum+1;
i++;}
(3) 表达式 3也可以省略,但此时程序设计者应另外设法保证循环能正常结束。如:
( 4)可以省略表达式 1和表达式 3,只有表达式 2,
即只给循环条件,如:
for(;i<=100;) while(i<=100)
{sum=sum+i; 相当于 {sum=sum+i;
i++;} i++;}
在这种情况下,完全等同于 while语句。可见 for语句比 while语句功能强,除了可以给出循环条件外,
还可以赋初值,使循环变量自动增值等。
( 5)三个表达式都可省略,如:
for(;;) 语句相当于
while(1)语句。
即不设初值,不判断条件 (认为表达式 2为真值 ),循环变量不增值。无终止地执行循环体。
(6) 表达式 1可以是设置循环变量初值的赋值表达式,
也可以是与循环变量无关的其他表达式。如,
for (sum=0;i<=100;i++) sum=sum+i;
表达式 3也可以是与循环控制无关的任意表达式。
表达式 1和表达式 3可以是一个简单的表达式,也可以是逗号表达式,即包含一个以上的简单表达式,中间用逗号间隔。如:
for(sum=0,i=1;i<=100;i++) sum=sum+i;

for(i=0,j=100;i<=j;i++,j--) k+=i*j;
表达式 1和表达式 3都是逗号表达式,各包含两个赋值表达式,即同时设两个初值,使两个变量增值,
执行情况见图 6.8。在逗号表达式内按自左至右顺序求解,整个逗号表达式的值为其中最右边的表达式的值。如,
for(i=1;i<=100;i++,i++) sum=sum+i;
相当于
for(i=1;i<=100;i=i+2) sum=sum+i;

图 6.9图 6.8
(7) 表达式一般是关系表达式 (如 i<=100)或逻辑表达式 (如 a<b && x<y),但也可以是数值表达式或字符表达式,只要其值为非零,就执行循环体。分析下面两个例子:
① for(i=0;(c=getchar())!='\ n';i+=c);
在表达式 2中先从终端接收一个字符赋给 c,然后判断此赋值表达式的值是否不等于‘\ n?(换行符 ),
如果不等于‘\ n?,就执行循环体。此 for语句的执行过程见图 6.9,它的作用是不断输入字符,将它们的 ascII码相加,直到输入一个“换行”符为止。
注意:此 for语句的循环体为空语句,把本来要在循环体内处理的内容放在表达式 3中,作用是一样的。
可见 for语句功能强,可以在表达式中完成本来应在循环体内完成的操作。
② for( ;(c=getchar())!='\ n';)
printf("%c",c);
只有表达式 2,而无表达式 1和表达式 3。其作用是每读入一个字符后立即输出该字符,直到输入一个
“换行”为止。请注意,从终端键盘向计算机输入时,是在按 Enter键以后才送到内存缓冲区中去的。运行情况:
computer (输入 )
computer (输出 )
而不是
ccoommppuutteerr
即不是从终端敲入一个字符马上输出一个字符,而是按 Enter键后数据送入内存缓冲区,然后每次从缓冲区读一个字符,再输出该字符。
从上面介绍可以知道 c语言中的 for语句比其他语言
(如 baSIc,PascaL)中的 fOR语句功能强得多。可以把循环体和一些与循环控制无关的操作也作为表达式 1或表达式 3出现,这样程序可以短小简洁。
但过分地利用这一特点会使 for语句显得杂乱,可读性降低,建议不要把与循环控制无关的内容放到 for语句中。
6.6 循环的嵌套一个循环体内又包含另一个完整的循环结构,称为循环的嵌套。内嵌的循环中还可以嵌套循环,这就是多层循环。各种语言中关于循环的嵌套的概念都是一样的。
三种循环 (while循环,do while循环和 for循环 )可以互相嵌套。例如,下面几种都是合法的形式:
(1) while( )
{…
while( )
{…}
}
(2) do
{…
do
{… }
while( );
}
while( );
(3) for(;;)
{
for(; ;)
{…}
}
(4) while( )
{…
do
{…}
while( );

}
(5) for(; ;)
{…
while( )
{ }

}
(6) do
{

for (; ;)
{ }
}
while( );
6.7 几种循环的比较
(1) 四种循环都可以用来处理同一问题,一般情况下它们可以互相代替。但一般不提倡用 goto型循环。
(2) while和 do while循环,只在 while后面指定循环条件,
在循环体中应包含使循环趋于结束的语句 (如 i++,或
i=i+1等 )。
for循环可以在表达式 3中包含使循环趋于结束的操作,
甚至可以将循环体中的操作全部放到表达式 3中。因此
for语句的功能更强,凡用 while循环能完成的,用 for
循环都能实现。
(3) 用 while和 do while循环时,循环变量初始化的操作应在 while和 do while语句之前完成。而 for语句可以在表达式 1中实现循环变量的初始化。
(4) while循型,do while循环和 for循环,可以用
break语句跳出循环,用 continue语句结束本次循环 (break语句和 continue语句见 6.8节 )。而对用
goto语句和 if语句构成的循环,不能用 break语句和 continue语句进行控制。
6.8.1 break语句在 4.4节中已经介绍过用 break语句可以使流程跳出
Switch结构,继续执行 Switch语句下面的一个语句。
实际上,break语句还可以用来从循环体内跳出循环体,即提前结束循环,接着执行循环下面的语句。如:
for(r=1;r<=10;r++)
{ area=Pi*r*r;
if(area>100) break;
printf("%f",area);
}
6.8 break语句和 continue语句计算 r=1到 r=10时的圆面积,直到面积 area大于 100为止。从上面的 for循环可以看到:当 area>100时,
执行 break语句,提前结束循环,即不再继续执行其余的几次循环。
break语句的一般形式为:
break;
break语句不能用于循环语句和 Switch语句之外的任何其他语句中。
6.8.2 continue语句一般形式为:
continue;
其作用为结束本次循环,即跳过循环体中下面尚未执行的语句,接着进行下一次是否执行循环的判定。
continue语句和 break语句的区别是,continue语句只结束本次循环,而不是终止整个循环的执行。而 break语句则是结束整个循环过程,不再判断执行循环的条件是否成立。如果有以下两个循环结构:
(1) while(表达式 1)
{ …
if(表达式 2) break;

}
(2) while(表达式 1)
{…
if(表达式 2) continue;

}
程序 (1)的流程图如图 6.10所示,而程序 (2)的流程如图 6.11所示。请注意图 6.10和图 6.11中当“表达式 2”
为真时流程的转向。
图 6.10 图 6.11
例 6.5把 100~ 200之间的不能被 3整除的数输出。
main()
{int n;
for (n=100;n<=200;n++)
{if (n%3==0)
continue;
printf("%d",n);
}
}
当 n能被 3整除时,执行 continue语句,结束本次循环
(即跳过 printf函数语句 ),只有 n不能被 3整除时才执行 printf函数。
当然,例 6.5中循环体也可以改用一个语句处理:
if (n%3!=0) printf("%d",n);
我们在程序中用 continue语句无非为了说明 continue
语句的作用。
6.9 程序举例例 6.6用 π/4≈1-13+15-
17+… 公式求 π的近似值,直到最后一项的绝对值小于 10-6为止。
用 N\|S结构化流程图表示算法 (见图 6.12)。
图 6.12
程序如下:
#include<math.h>
main()
{
int s;
float n,t,Pi;
t=1; Pi=0;n=1.0;S=1;
while((fabs(t))>1e-6)
{Pi=Pi+t;
n=n+2;
s=-s;
t=S/n;
}
Pi=Pi*4;
printf("Pi=%10.6f\ n",Pi);
}
运行结果为:
Pi= 3.141594
例 6.7求 fibonacci数列 40个数。这个数列有如下特点:
第 1,2两个数为 1,1。从第 3个数开始,该数是其前面两个数之和。即,
f1=1 (n=1)
f2=1 (n=2)
fn=fn-1+fn-2 (n≥3)
这是一个有趣的古典数学问题:有一对兔子,从出生后第 3个月起每个月都生一对兔子。小兔子长到第 3个月后每个月又生一对兔子。假设所有兔子都不死,问每个月的兔子总数为多少?
解此题的算法如图 6.13所示。
图 6.13
程序如下:
main()
{
long int f1,f2;
int i;
f1=1;f2=1;
for(i=1; i<=20; i++)
{
printf("%12ld %12ld ",f1,f2);
if(i%2==0) printf("\ n");
f1=f1+f2;
f2=f2+f1;
}
}
运行结果为:
1 1 2 3
5 8 13 21
34 55 89 144
233 377 610 987
1597 2584 4181 6765
10946 17711 28657 46368
75025 121393 196418 317811
514229 832040 1346269 2178309
3524578 57022887 9227465 14930352
24157817 39088169 63245986 102334155
图 6.14
程序中在 printf函数中输出格式符用,%12 ld”,而不是用,%12d”,这是由于在第 23个数之后,整数值已超过整数最大值 32767,因此必须用,%ld”
格式输出。 if语句的作用是使输出 4个数后换行。 i
是循环变量,当 i为偶数时换行,而 i每增值 1,就要计算和输出 2个数 (f1,f2),因此 i每隔 2换一次行相当于每输出 4个数后换行输出。
例 6.8判断 m是否素数。算法如图 6.14所示。
我们采用的算法是这样的:让 m被 2到 m 除,如果 m
能被 2~ m 之中任何一个整数整除,则提前结束循环,此时 i必然小于或等于 k(即 m);如果 m不能被 2~ k(即 m)之间的任一整数整除,则在完成最后一次循环后,i还要加 1,因此 i=k+1,然后才终止循环。在循环之后判别 i的值是否大于或等于 k+1,
若是,则表明未曾被 2~ k之间任一整数整除过,
因此输出“是素数”。
程序如下:
#include <math.h>
main()
{
int m,i,k;
scanf("%d",&m);
k=sqrt(m+1);/*加 1是为了避免在求 m时可能出现的误差 */
for (i=2;i<=k;i++)
if(m%i==0) break;
if(i>=k+1) printf("%d is a Prime muber\ n",
m);
else printf("%d is not a Prime number\ n",
m);
}
运行情况如下:
17
17 is a Prime number
例 6.9求 100~ 200间的全部素数。
在例 6.8的基础上,对本题用一个嵌套的 for循环即可处理。程序如下,# include <math.h>
main()
{
int m,k,i,n=0;
for(m=101;m<=200;m=m+2)
{
k=sqrt(m);
for (i=2;i<=k;i++)
if (m%i==0) break;
if (i>=k+1){printf("%d ",m);n=n+1;}
if(n%10==0) printf("\ n");
}
printf ("\ n");
}
运行结果如下:
101 103 107 109 113 127 131 137 139 149
151 157 163 167 173 179 181 191 193 197
199
n的作用是累计输出素数的个数,控制每行输出 10
个数据。
例 6.10译密码。为使电文保密,往往按一定规律将其转换成密码,收报人再按约定的规律将其译回原文。例如,可以按以下规律将电文变成密码:
将字母 a变成字母 E,a变成 e,即变成其后的第 4个字母,W变成 a,X变成 b,Y变成 c,Z变成 D。见图
6.15。字母按上述规律转换,非字母字符不变。如
,china!”转换为,Glmre!” 。
输入一行字符,要求输出其相应的密码。
图 6.15
程序如下:
#include <Stdio.h>
main()
{
char c;
while((c=getchar())!='\ n')
{
if((c>='a' && c<='z') || (c>='a' && c<='Z'))
{ c=c+4;
if(c>'Z' && c<='Z'+4 || c>'z') c=c-26;
}
printf("%c",c);
}
}
运行结果如下:
china!
Glmre!
程序中对输入的字符处理办法是:先判定它是否大写字母或小写字母,若是,则将其值加 4(变成其后的第 4个字母 )。如果加 4以后字符值大于‘ Z?或
‘ z?,则表示原来的字母在 V(或 v)之后,应按图
6.15所示的规律将它转换为 a~ D(或 a~ d)之一。办法是使 c减 26,如果读者对此还有疑问,请查 ascII码表即可清楚。还有一点请读者注意:内嵌的 if语句不能写成
if(c>'Z'|| c>'z') c=c-26;
因为当字母为小写时都满足,c>?Z?”条件,从而也执行,c=c-26;”语句,这就会出错。
因此必须限制其范围为,c>?Z? && c<=?Z?+4”,即原字母为‘ W? 到‘ Z?,在此范围以外的不是大写字母 W~ Z,不应按此规律转换。请考虑:为什么对小写字母不按此处理,即写成
c>'z' && c<='z'+4
而只须写成,c>?z?”即可。
6.10 习题
6.1 输入两个正整数 m和 n,求其最大公约数和最小公倍数。
6.2 输入一行字符,分别统计出其中英文字母、空格、数字和其他字符的个数。
6.3 求 Sn=a+aa+aaa+…+aa…a n 个 a之值,其中 a是一个数字。例如,2+22+222+2222+22222(此时 n=5),
n由键盘输入。
6.4 求 (即求 1!+2!+3!+4!+…+20!) 。
6.5 打印出所有的“水仙花数”,所谓“水仙花数”
是指一个 3位数,其各位数字立方和等于该数本身。
例如,153是一水仙花数,因为 153=13+53+33。
6.6 一个数如果恰好等于它的因子之和,这个数就称为“完数”。例如,6的因子为 1,2,3,而
6=1+2+3,因此 6是“完数”。编程序找出 1000之内的所有完数,并按下面格式输出其因子:
6itS factorS are 1,2,3
6.7 有一分数序列
2/1,3/2,5/3,8/5,13/8,21/13,…
求出这个数列的前 20项之和。
6.8 猴子吃桃问题。猴子第一天摘下若干个桃子,
当即吃了一半,还不过瘾,又多吃了一个。第二天早上又将剩下的桃子吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩下的一半零一个。到第
10天早上想再吃时,就只剩一个桃子了。求第一天共摘多少桃子。
6.9 用迭代法求 。求平方根的迭代公式为要求前后两次求出的 x的差的绝对值小于 10-5。
6.10 打印出以下图案。
*
* * *
* * * * *
* * * * * * *
* * * * *
* * *
*