C语言-第7章-流程控制之循环结构

7.0 导入例子

假设班级中有100名同学,希望输入他们的数学成绩并计算平均成绩,程序应该怎样写呢?

计算平均分用总分除以总人数即可,可以定义一个变量sum来记录总分数,以及一个分数变量score来保存输入的某个同学成绩。求和之前,sum要设置为0。输入一名同学的成绩到score中,加到sum上去,然后继续这个过程,直至100名同学的成绩都这样处理完毕,sum中就是全部同学的成绩的和。

int sum = 0, score;

scanf("%d", &score);
sum += score;

scanf("%d", &score);
sum += score;

...

可以想象,如果要计算100名同学的成绩,输入和求和的代码要写100次,那一千名同学,一万名同学还是这样处理吗?这种情况下适合使用循环语句。

int sum = 0, score, count;

count = 0;

while(count < 100){
  scanf("%d", &score);
  sum += score;
  count++;
}

printf("平均分是%d\n", sum / 100);

while循环语句只要小括号里面的条件为真,就会执行大括号里面的语句,大括号里面的语句也称为循环体。具体来说,判断循环条件是否为真,为真就执行循环体,然后继续判断循环条件是否为真,为真就执行循环体,…,直到某次循环条件为假,不执行循环体,循环结束,执行循环之后的语句。如果第一次判断条件式子就为假,那么循环体一次也不执行就结束了。

循环结构和选择结构语法上是比较类似的,都是在条件式子为真时会执行一些语句,不同的是,循环结构在执行完循环体中的语句后会再次回到循环语句开头的地方,再来一次刚才的过程,就像整个循环语句是初次执行一样。循环结构用这种方式来实现反复执行一些语句的功能。

就像选择语句可以嵌套一样,循环语句也可以嵌套(当然循环语句里面也可以嵌套选择语句)。比如,如果要计算5门课程的平均分,我们可以将计算一门课程平均分的过程重复5次,

int sum, score, count;

// 计算第一门课的平均分
count = 0;
sum = 0;

while(count < 100){
  scanf("%d", &score);
  sum += score;
  count++;
}

printf("平均分是%d\n", sum / 100);

// 计算第二门课的平均分
count = 0;
sum = 0;

while(count < 100){
  scanf("%d", &score);
  sum += score;
  count++;
}

printf("平均分是%d\n", sum / 100);

// 计算第三门课的平均分
...

这里又出现了重复的流程,只不过重复的流程本身变的复杂了,本身含有循环,但是这并不影响将它放入循环中。

int sum, score, count_student, count_lesson;

count_lesson = 0;

while(count_lesson  < 5){

  // 计算第count_lesson门课的平均成绩-开始
  count_student = 0;
  sum = 0;
  
  while(count_student < 100){
    scanf("%d", &score);
    sum += score;
    count_student ++;
  }

  printf("第%d门课的平均分是%d\n", count_lesson, sum / 100);
  // 计算第count_lesson门课的平均成绩-结束
  
  count_lesson++;  // 不能忘记对它自增1,否则就是无限循环了
}

外层循环负责将计算一门课平均分的流程应用到各门课中,内层循环负责具体的一门课的总分的统计。

7.1 while循环

怎样计算半径从1、2、3、…100的各个圆的面积呢?可以把单个计算圆的面积的代码重写100遍,只是半径值修改为1到100,但是这样除了半径值不同,其余代码都是重复的

int radius, 
double area;

radius = 1;
area = PI * radius * radius;
printf("半径为%d圆的面积是%.2lf\n", radius, area);

radius = 2;
area = PI * radius * radius;
printf("半径为%d圆的面积是%.2lf\n", radius, area);

...

radius = 100;
area = PI * radius * radius;
printf("半径为%d圆的面积是%.2lf\n", radius, area);

可以使用while语句完成

int radius;
double area;

radius = 1;
while(radius <= 100){
   area = PI * radius * radius;
   printf("半径为%d圆的面积是%.2lf\n", radius, area);
   radius++;
}

代码的执行情况如:

radius赋值为1
radius <=100成立,故进入循环体计算并输出圆面积,radius自增1,等于2
radius <=100成立,故进入循环体计算并输出圆面积,radius自增1,等于3

radius <=100成立,故进入循环体计算并输出圆面积,radius自增1,等于99
radius <=100成立,故进入循环体计算并输出圆面积,radius自增1,等于100
radius <=100成立,故进入循环体计算并输出圆面积,radius自增1,等于101
radius <= 100不成立,故while循环结束,执行while语句后面的语句。

可以看到,while相比if语句是很类似的,只是if语句只会计算条件式子一次,执行完if或者else分支后if语句结束,而while会重复判断条件式子和执行循环体,直到某次条件式子为假,循环才结束。

while语句的基本形式是:

while(条件式子){
    语句
    ...
}

while

while-circle-area
while语句与if语句类似,如果循环中要执行多条语句,要用大括号{}将它们括起来,否则循环体就只包含那条紧跟在小括号后面的语句。比如上述例子代码错误的没写大括号:

int radius;
double area;

radius = 1;
while(radius <= 100)
   area = PI * radius * radius;
   printf("半径为%d圆的面积是%.2lf\n", radius, area);
   radius++;

那么只有跟在小括号后面的area = PI * radius * radius;语句是属于while语句的,是循环体,而后面两条语句是while语句后面的不属于循环体的语句了,故此时反映代码关系的缩进是

int radius;
double area;

radius = 1;
while(radius <= 100)
   area = PI * radius * radius;
printf("半径为%d圆的面积是%.2lf\n", radius, area);
radius++;

流程图变成了
while-circle-area-missbrace
这种情况下,循环是死循环,想想为什么?

下面给出几个使用while循环语句的例子

输出"Hello C!"10次。

int i;
i = 0;
while(i < 10){
  printf("Hello C!\n");
  i++;
}

当然,i可以不从0开始,从1开始也可以,关键的是使循环体执行10次。

int i;
i = 1;
while(i <= 10){
  printf("Hello C!\n");
  i++;
}

注意,此时条件式子从i < 10变为了i <= 10,这样i等于10时,条件还成立,确保循环体能执行10次。

编程计算1到10的和。

int i, sum;
i = 1;
sum = 0;
while(i <= 10){
  sum += i;
  i++;
}

7.1.1 while循环相关的3个部分

通过上述例子我们可以看到,尽管循环语句相比if语句要复杂,但是为了完成某个功能,其语句的逻辑构成还是有规律的。实际编程中,除了循环体以外,循环语句往往有3个逻辑组成部分。具体来说,就是初始化、条件、额外控制这样的3个部分。

循环初始化语句
while(循环条件){
    循环体语句
    ...
    控制循环额外语句
}

while-3-parts

比如

int radius;
double area;

radius = 1;  //  初始化语句
while(radius <= 10){  // 括号里是循环条件
   // 下面两句是循环体语句
   area = PI * radius * radius;    
   printf("半径为%d圆的面积是%.2lf\n", radius, area); 
   radius++;  // 控制循环额外语句
}
int i;
i = 0;  //  初始化语句
while(i < 10){  // 括号里是循环条件
  printf("Hello C!\n");  // 循环体语句
  i++;   // 控制循环额外语句
}
int i, sum;  
i = 1;   //  初始化语句
sum = 0;  //  初始化语句
while(i <= 10){  // 括号里是循环条件
  sum += i;   // 循环体语句
  i++;  // 控制循环额外语句
}

这三个部分和循环体一起,是紧密联系的,共同让循环语句实现我们期望的功能。如果缺少了或者写错了,就会出现各种错误。以求1到10的和为例,如果忘记在求和之前对sum赋值为0,那么会出现运行时错误,指出没有对sum初始化。或者赋值sum为其它值,那么最终算出的和一定不对。而s = 1;i <= 0i++它们三者共同控制循环执行10次,其中i从1遍历到10。它们的缺失或者写错都会对循环结果产生影响。

可见,这几个部分深刻影响着while循环,这也是我们在写所有循环语句(包括后面要介绍的do-while和for循环)时要注意的地方。初学者写循环语句还是有一定难度的,可以从循环体以及这3个逻辑部分来考虑。比如,下面给出一个写循环语句的策略:

  1. 找出循环体语句
  2. 将循环体写到无限循环中
    while(1){
        循环体语句
        ...
    }
    
  3. 补充循环条件、初始化语句、控制循环额外语句
    循环初始化语句
    while(循环条件){
        循环体语句
        ...
        控制循环额外语句
    }
    
  4. 测试循环,找出错误

比如,对于求1到10的和

  1. 找出循环体语句
    sum += i;
  2. 将循环体语句写到无限循环中
    while(1){
     sum += i;
    }
    
  3. 补充循环条件、初始化语句、控制循环额外语句
    i = 1;   //  初始化语句
    sum = 0;  //  初始化语句
    while(i <= 10){  // 括号里是循环条件
       sum += i;   // 循环体语句
       i++;  // 控制循环额外语句
    }
    
  4. 测试循环,找出错误

找出循环体语句还是比较关键的,比如如果想成了sum = 1 + 2 + ... + 10 + ...用这样的方式来计算和,就无法使用循环了。很多时候,循环体语句中要使用变量,而循环语句能让这个变量的值发生有规律的改变。

7.1.2 案例-猜数字

程序随机产生一个在0到100之间的整数。用户反复猜这个数,直到猜中为止。每次猜测后,程序都给出提示,猜中了、猜大了还是猜小了。如果不能一次写出循环,可以先出猜一次的代码

#include <stdio.h>
#include <stdlib.h>  // 包含rand和srand函数的使用说明
#include <time.h>  // 包含time函数的使用说明

int main() {
	int magic, guess;
	
    srand(time(NULL));
    magic = rand() % 101;
    
    printf("...产生了一个0到100之间的随机数\n");
    
    printf("输入你的猜测:");
    scanf("%d", &guess);
    	
    if (guess == magic){
    	printf("猜对了\n");
	}else if(guess < magic){
		printf("猜小了\n");
	}else{
		printf("猜大了\n");
	}    
    
	return 0;
}

其中rand()函数返回一个范围在 0 到 RAND_MAX 之间的伪随机数。为了让每次程序运行产生的随机数不同,需要向srand()函数输入一个不同值的“种子”(换句话说,如果向srand()函数输入的值相同,那么rand()产生的随机数序列是相同的)。而time(NULL)函数返回当前时间(自1970-01-01 00:00:00 UTC起经过的时间,以秒为单位),故每次运行程序这个函数的返回的值是不同的。将这个值传递给srand函数,将产生不同的随机数序列。

怎样将程序改为循环猜测?如果不能一次写出来,可以按步骤改写代码,先将循环体放到无限循环中

while(1){
    printf("输入你的猜测:");
    scanf("%d", &guess);
    	
    if (guess == magic){
    	printf("猜对了\n");
	}else if(guess < magic){
		printf("猜小了\n");
	}else{
		printf("猜大了\n");
	}    
}

然后再修改循环条件,添加循环初始化和循环额外控制。完整的循环代码:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main() {
	int magic, guess;
	
    srand(time(NULL));
    magic = rand() % 101;
    
    printf("...产生了一个0到100之间的随机数\n");
    
    guess = -1;
    while(guess != magic){
    	printf("输入你的猜测:");
    	scanf("%d", &guess);
    	
    	if (guess == magic){
    		printf("猜对了\n");
		}else if(guess < magic){
			printf("猜小了\n");
		}else{
			printf("猜大了\n");
		}
	}
    
	return 0;
}

其中,产生随机数代码和guess = -1;是循环初始化语句,想想为什么guess要赋值为-1,赋值为1可以吗?这个例子中并没有控制循环额外语句。

7.1.3 案例-序列求和

计算一系列输入整数的和。

程序只需要知道所有输入的整数的和,而不需要知道每个整数是多少,所以我们可以只定义一个变量x用来存储各次输入的值,定义一个变量sum用来记录和。而输入和求和是重复进行的,故可以使用循环语句来完成。

由于程序中没有具体指定怎样输入一系列整数,这里结合一些常见循环输入的处理,假设有3种方案:

  1. 整数序列的数量固定
  2. 用户输入’y’或者’n’确定是否输入结束
  3. 用户输入0这个特殊的整数值表示输入结束

下面分别给出对应每种方案的处理代码。

  1. 整数序列的数量固定

    int i, n, sum, x;
    
    printf("请输入数量:");
    scanf("%d", &n);
    i = 0;
    sum = 0;
    
    while(i < n){
      printf("请输入第个%d数:", i + 1);
      scanf("%d", &x);
      sum += x;
      i++;
    }
    
    printf("全部整数的和是%d\n", sum);
    
  2. 用户输入’y’或者’n’确定是否输入结束
    可以在输入一个整数后,提示用户继续输入字符’y’或者’n’来判定是否继续输入。

    int x, sum;
    char c;
    
    c = 'y';
    sum = 0;
    
    while(c == 'y'){
        printf("请输入一个整数:");
        scanf("%d", &x);
        sum += x;
        printf("继续输入?('y'表示继续, 'n'表示结束):");
        scanf(" %c", &c);
    }
    
    printf("全部整数的和是%d\n", sum);
    

    注意,输入字符的scanf语句中,格式字符串是"<空格>%c"(对应输入字符类型的值),%c之前有一个空格,这个空格用来和换行符等空白字符匹配,这个换行符是上一个scanf("%d", &x);语句运行时用户输入留下的,这样用户输入的y或者’n’才能被c读取。假如这条语句没有这个空格scanf("%c", &c);,那么scanf会将留在输入缓冲区的换行字符读到字符变量c中。

  3. 用户输入特殊值0表示输入结束
    假设整数序列中本身是没有0值,那么我们可以用0来表示输入整数序列结束了。

    int x, sum;
    x = -1;
    sum = 0;
    while(x != 0){
         printf("请输入整数值:");
         scanf("%d", &x);
         if (x != 0){
             sum += x;
         }
    }
    printf("全部整数的和是%d\n", sum);
    

    也可以写成

    int x, sum;
    sum = 0;
    printf("请输入整数值:");
    scanf("%d", &x);
    while(x != 0){	      
         sum += x;
         printf("请输入整数值:");
      	 scanf("%d", &x);
    }
    printf("全部整数的和是%d\n", sum);
    

7.2 do-while循环

do-while循环语句是while循环语句的变体,其基本语法是:

do{
    语句
    ...
}while(条件式子);

do-while语句的流程是先执行循环体,然后再计算条件式子的值,如果为真,继续执行循环体,再计算表达式的值…。如果条件式子为假,停止执行循环体语句,do-while语句结束,执行之后的语句。

do-while

do-while语句是类似于while语句的,最大的不同是while语句先计算表达式的值,后根据表达式的值决定是否执行循环体,而do-while的顺序是先执行一遍循环体,后计算表达式的值,后根据表达式的值决定是否执行循环体。可以看到,do-while的流程是可以用while来完成的,即

do{
    循环体语句
}while(表达式);

等价于

循环体语句
while(表达式){
    循环体语句
}

不少情况下,while循环可以基本不变的修改为do-while循环,比如

int i, sum;
i = 1, sum = 0;

do{
	sum += i;
	i++;
}while(i <= 10);

请思考什么情况下,while可以用do-while替代?

由于while的循环体可能一次都不执行,而do-while的循环体至少会执行一次。故如果希望循环体至少执行一次,可以用do-while替换while循环。

例1. 计算非负整数的位数。

比如,输入345,输出3;输入5,输出1。

当整数比较小的时候,我们一眼就可以看出来它的位数,但是如果整数比较大,比如68463246,甚至更长的整数,为了避免出错,我们就可能一个位一个位的数出来,这就给我们提供了提示,让计算机数整数的位数。计算机怎样数呢?我们知道对整数除以10,其位数会少一位,比如68463246/10等于6846324。故如果重复的对整数除以10,直到整数变为0,重复的次数就是整数的位数。

下面给出了重复对一个整数除10的while循环

int x;
scanf("%d", &x);

while(x != 0){
    x /= 10;
}

想要知道循环重复几次可以定义整数变量来统计

int digits= 0;
int x;
scanf("%d", &x);

while(x != 0){
    x /= 10;
    digits++;
}

printf("位数是%d\n", digits);

我们可以使用一些整数来测试,比如123

循环次数xdigits
01230
1121
212
303

这样,程序基本完成,不过还有一个问题,如果整数为0,程序运行的结果是0,但是从位数来看,0应该与个位数一样是1位比较合适,怎么处理呢?

可以使用if语句对0做特殊处理,不过注意到如果是0的话也让循环体执行一次,那么算出来就是1了,这就提醒我们可以用do-while语句来尝试一下

int digits = 0;
int x;
scanf("%d", &x);

do{
    x /= 10;
    digits++;
}while(x != 0);

printf("位数是%d\n", digits);

改写为do-while循环后,再测试下程序。

7.3 for循环

介绍while语句的时候提到过循环语句除了循环体,还有3个逻辑部分与循环密切相关,就是循环初始化语句、循环条件、控制循环额外语句。

循环初始化语句
while(循环条件){
    循环体语句
    ...
    控制循环额外语句
}

而for语句也是一种while循环语句的变体,而它着重强调了这3块内容。

for (循环初始化语句; 循环条件; 控制循环额外语句){
    循环体语句
    ...
}

for
for语句可以看成自带编写“循环初始化语句”和“控制循环额外语句”的地方的while语句。本质上,for语句等价于while语句。

比如,计算1到10的和的程序

int i, sum;
i = 1, sum = 0;
while(i <= 10){
  sum += i;
  i++;
}

写成for循环的形式:

int i, sum;
for(i = 1, sum = 0; i <= 10; i++){
  sum += i;
}

同样,for语句也可以改写为while语句。

这里要说明几点:

  • for循环里的小括号中一定要使用两个分号;,而且只能出现两个分号。它们将小括号里的内容分为3部分。下面我们称它们为第1、2、3部分:for(第1部分; 第2部分; 第3部分)...
  • 小括号里的第1部分和第3部分可以放置多条语句,但是要用逗号,把它们隔开,不能使用常规的分号;,因为分号在for语句中的作用是将小括号里面的内容分为3部分。
  • 小括号里的第1部分和第3部分可以留空,留空意味着什么都不做,从语法上来说没有任何问题。比如计算1到10的和的代码可以改写为
    int i, sum;
    i = 1, sum = 0;
    for(  ; i <= 10; ){
       sum += i;
       i++;
    }
    
    其实,这个for语句就相当于“退化”为while语句了。
  • 小括号里第2部分为放置循环条件的表达式,其含义与while循环小括号中的表达式是一致的,表达式值的真或者假决定循环是否继续。这里for与while不同的是这部分可以留空,for(第1部分; ; 第3部分)...,留空默认为真,即for(第1部分; 1 ; 第3部分)...。因此,for语句for(;;)...是形式上的无限循环语句,相当于while语句while(1)...。注意,while语句中小括号里的表达式是不允许为空的。
  • 在C99标准下,for语句小括号的第1部分可以替换为一个声明,但是这样声明的变量只属于for语句块,故该变量只能在循环内访问,不可以在循环外访问。
    int sum = 0;
    for(int i = 0; i <= 10; i++){
       sum += i;
    }
    printf("i = %d\n", i);  // 错误,因为i声明在for中,只能在for中访问
    printf("sum = %d\n", sum);  // 正确
    
    循环中,变量i从1遍历到了10,并依次加到了sum变量中。因为在循环语句之后只需要访问sum变量,而不需要访问i变量,故它可以声明在for语句的第1部分。但是sum变量是不应该声明在for语句的第1部分的,因为for语句结束之后我们还要访问它。
    在VS2010的for语句里面不用担心这个问题,因为它不支持在第一部分声明变量!
    在C99标准下,for语句的小括号的第1部分可以声明多个变量,只要它们的类型相同
     for(int i = 0, j = 0; i <= 10; )...
    
    上述语句中,for内部声明了两个整型变量i和j。for内部可以访问它们。离开for语句以后就不能访问它们了。

例1. 打印平方表

程序提示用户输入一个整数n,输出从1到n之间各个数的平方,一行显示一个整数和其平方。如果使用while循环来完成,代码如下

#include <stdio.h>

int main()
{
	int i, n;

	printf("请输入整数:");
	scanf("%d", &n);

    i = 1;
	while(i <= n){
		printf("%d*%d=%d\n", i, i, i * i);
		i++;
	}

	getch();
    getch();
	return 0;
}

像这样的其循环次数是已知(固定)的循环,非常适合用for来实现,因为与循环相关的3部分很明确。下面的代码该用for语句来实现。

#include <stdio.h>

int main()
{
	int i, n;

	printf("请输入整数:");
	scanf("%d", &n);

	for(i = 1; i <= n; i++){
		printf("%d*%d=%d\n", i, i, i * i);
	}

	getch();
    getch();
	return 0;
}

例2. 找出,反映穷举的思想

例3. 判断一个数是否是素数

7.3.1 for语句小括号中的逗号表达式

出现在for语句小括号第1部分或者第3部分的多条语句需要用逗号隔开,在C语言中,逗号运算符组成了逗号表达式。在第6章运算符和表达式中介绍了逗号表达式,它的优先级是最低的,结合性是左结合,最后整个表达式的值是最右边式子的值。在for语句小括号第1部分以及第3部分的多条语句形成的逗号表达式并没有使用到整个逗号表达式的值(最右边式子的值),那里的逗号表达式的作用只是从左到右处理各个子表达式。

7.4 使用哪个循环

目前为止,介绍了3种循环语句,while、do-while和for。那么在编程中选择哪个来实现循环语句呢?其实,在很多情况下,使用哪个都可以。形式看,while语句和for语句可以相互转换,do-while语句也可以写成while语句,如果while语句的条件式子第一次判断为真,那么可以改写成do-while语句。

不过因为它们的特点还是稍有不同的,在具体的场景下,特定的循回可能更合适。比如,如果循环次数已知(包括循环次数在程序运行过程中输入到程序中的情况),可以考虑使用for语句,如果循环体至少要执行一次,可以考虑使用do-while,否则,可以用最基本的while语句。

if (循环次数已知)
  使用for循环
else
  if (循环体至少执行一次 || 循环条件需要在循环体中明确)
    使用do-while循环
  else
    使用while循环

例1. 计算一系列输入整数的和的程序中,第2种方案假设用户输入’y’或者’n’确定是否输入结束。下面的代码是用while实现的版本

int x, sum;
char c;

c = 'y';
sum = 0;

while(c == 'y'){
    printf("请输入一个整数:");
    scanf("%d", &x);
    sum += x;
    printf("继续输入?('y'表示继续, 'n'表示结束):");
    scanf(" %c", &c);
}

printf("全部整数的和是%d\n", sum);

这个循环体是至少需要执行一次的,这就是合适使用do-while来实现的一个标志,

int x, sum;
char c;

sum = 0;

do{
    printf("请输入一个整数:");
    scanf("%d", &x);
    sum += x;
    printf("继续输入?('y'表示继续, 'n'表示结束):");
    scanf(" %c", &c);
}while(c == 'y');

printf("全部整数的和是%d\n", sum);

例2. 计算一系列输入整数的和的程序中,第1种方案假设用户输入的整数序列的数量固定。下面的代码是用for实现的版本

int i, n, sum, x;

printf("请输入数量:");
scanf("%d", &n);

for(i = 0, sum = 0; i < n; i++){
  printf("请输入第个%d数:", i + 1);
  scanf("%d", &x);
  sum += x;
}

printf("全部整数的和是%d\n", sum);

显然,依赖计数的循环使用for语句更能凸显计数开始边界和结束边界,以及每次计数变量的更新情况。

7.5 循环常见问题

  1. 大括号问题
    如果循环体中包含了多条语句,不要忘记用大括号将它们括起来,这样逻辑上它们是一条语句,即循环体语句。这与if语句中的情况是类似的。建议不论循环体中有几条语句,都打上大括号。

  2. 分号问题:while和for语句中小括号后面一定不要有分号,do-while小括号后面一定要加分号
    while或者for后面如果加了分号
    while(表达式);{
    }
    那么分号;形成的空语句就变成了循环体,而其后的语句就与循环语句无关了。这样循环很可能变为死循环。这与if语句中情况也是类似的。
    与while和for语句相反,语法上要求do-while语句小括号后面加上分号;

    do{
      ...
    }while(...);
    

    遗漏的话编译出错。

  3. for小括号里面有且仅有2个分号
    不论for语句中有没有循环初始化、循环条件以及额外控制循环部分,小括号里面必须有而且只能有2个分号

    for( ... ; ... ; ...)
    
  4. C99中标准支持在for的第1部分中声明变量。但是这个变量在for语句之外就不能访问了。如果这里声明的变量名和for语句外部的变量重名了,那么在for语句内部访问的变量将是for语句第1部分中声明的变量,换句话说,在for语句内部,for语句第1部分中声明的变量将遮盖住for语句外部的同名变量。比如

    int sum = 0;
    
    for(int i = 1, sum = 0; i <= 10; i++){
    	sum += i;
    }
    
    printf("sum = %d\n", sum);
    

    上述代码运行结果将是0,因为从1到10的和累加到了for语句中声明的变量sum中去了,而for语句之外声明的变量sum始终未0。
    这里很细微的点是在for语句的第1部分的代码int i = 1, sum = 0的含义是同时声明变量isum,这两个变量只能在for语句中使用。这条代码很容易让人误以为只是在声明变量i,而不是声明变量sum,只是对在for语句外部声明的变量sum进行赋值。注意,这样的写法的含义是同时声明新的变量isum。在VS2010中不会有这个问题,因为VS2010不支持在for语句的第一部分声明变量:-)。
    在for的第1部分中声明变量在for语句之外不能访问,而且会屏蔽for语句之外的同名变量。这个现象与大括号会定义一个语句块,块内变量在块外就不能访问,而且语句块内的变量会“屏蔽”块外的同名变量是类似的。比如,下面程序编译报错

     #include <stdio.h>
     
     int main()
     {
     	{               // 大括号定义了一个语句块
     		int sum;      // 块内声明的变量sum
     
     		sum = 10;      // 块内的sum赋值为10
     
     		printf("in sum:%d\n", sum);  // 显示块内的sum的值
     	}           // 语句块的结束 
     	printf("out sum:%d\n", sum);  // 编译报错的语句,因为块外无法访问sum
     
     	getchar();
     	return 0;
     }
    

    下面程序的运行结果是

    #include <stdio.h>
    
    int main()
    {
    
    	int sum = 0;
    	{               // 大括号定义了一个语句块
    		int sum;      // 块内声明的变量sum屏蔽了外部的sum
    
    		sum = 10;      // 块内的sum赋值为10
    
    		printf("in sum:%d\n", sum);
    	}           // 语句块的结束
    	printf("out sum:%d\n", sum);  // 显示块外的sum
    
    	getchar();
    	return 0;
    }
    

    在这里插入图片描述

7.6 循环控制语句break和continue

switch语句中已经出现了break语句,在循环语句中也可以使用break和continue语句控制循环,循环体中执行break,可以提前终止整个循环。循环体中执行continue,会跳过本次循环体剩余语句。

break的例子如下,程序计算从1到10的和,但是如果求和过程中和超过了20,提前终止整个循环。

#include <stdio.h>

int main()
{
	int i, sum = 0;
	for(i = 1; i <= 10; i++){
	  sum += i;
	  if (sum > 20){
	  	break;
	  }
	}
	printf("i = %d,sum = %d", i, sum);
	return 0;
}

循环体中的continue的作用不是终止整个循环,而是跳过本次循环中循环体剩余的语句。比如,下面的程序计算从1到10的和,但是跳过对5和6求和。

#include <stdio.h>

int main()
{
	int i, sum = 0;
	for(i = 1; i <= 10; i++){	  
	  if (i == 5 || i == 6){
	  	continue;
	  }
	  sum += i;
	}
	printf("i = %d,sum = %d", i, sum);
	return 0;
}

本质上,可以不用break和continue也能达到同样的功能,比如,上述两个例子可以分别修改为

#include <stdio.h>

int main()
{
	int i, sum = 0;
	int over = 0;
	for(i = 1; i <= 10 && !over;){
	  sum += i;
	  if (sum > 20){
	  	over = 1;
	  }else{
	    i++;
	  }
	}
	printf("i = %d,sum = %d", i, sum);
	return 0;
}
#include <stdio.h>

int main()
{
	int i, sum = 0;
	for(i = 1; i <= 10; i++){	  
	  if (i != 5 && i != 6){
	  	sum += i;
	  }	  
	}
	printf("i = %d,sum = %d", i, sum);
	return 0;
}

所以,可以看出使用break和continue的可以简化代码和让逻辑更加清晰。但是,过多使用break和continue语句会使循环有太多的“出口”,反而会降低代码的可读性。

7.7 循环嵌套

循环体内可以有多条语句(需要括在大括号里面)。这也包括了循环语句自身。有些情况下,需要像选择语句嵌套那样,循环语句内部还嵌套着循环语句。

比如,输出3行5列的*

#include <stdio.h>

int main(void)
{
	int i, j;

	for(i = 0; i < 3; i++){
		for(j = 0; j < 5; j++){
			printf("*");
		}
	    printf("\n");
	}

	getchar();
	return 0;
}

在这里插入图片描述

在这里插入图片描述

下面的演示程序会输出外层和内层循环的计数变量i和j值。

#include <stdio.h>

int main(void)
{
	int i, j;

	for(i = 0; i < 3; i++){
		for(j = 0; j < 5; j++){
			printf("%d-%d ",i, j);
		}
		printf("\n");
	}

	getchar();
	return 0;
}

在这里插入图片描述
外层循环会让i从0依次遍历到2,这样循环体就执行3次,而每次循环体执行,又会运行内层循环语句,内层循环会让j从0依次遍历到4,故内层循环的循环体会执行5次,所以有以上输出结果。

对上面的程序稍作改动,就可以输出9*9乘法口诀表

#include <stdio.h>

int main(void)
{
	int i, j;

	for(i = 1; i <= 9; i++){
		for(j = 1; j <= 9; j++){
			printf("%d*%d=%d\t", i, j, i * j);
		}
	    printf("\n");
	}

	getchar();
	return 0;
}

在这里插入图片描述

注意,break和continue只对它们所在的循环起作用。如果它们在嵌套循环的内层循环中,对外层循环是没有影响的。比如,打印9*9乘法口诀表,在只打印下三角形的口诀表,可以写为

#include <stdio.h>

int main(void)
{
	int i, j;

	for(i = 1; i <= 9; i++){
		for(j = 1; j <= i; j++){
			printf("%d*%d=%d\t", i, j, i * j);
		}
	    printf("\n");
	}

	getchar();
	return 0;
}

在这里插入图片描述

也可以不改变内层循环的for语句的循环条件,而是在循环体里面增加break语句,提前结束内层循环。

#include <stdio.h>

int main(void)
{
	int i, j;

	for(i = 1; i <= 9; i++){
		for(j = 1; j <= 9; j++){
			if (j > i){
				break;
			}
			printf("%d*%d=%d\t", i, j, i * j);
		}
	    printf("\n");
	}

	getchar();
	return 0;
}

break只会跳出内层循环,即结束打印一行,而不是结束打印整个乘法口诀表。同样的,内层循环的continue语句只会跳过内层循环本轮还未执行的循环体语句,到下一轮循环,对外层循环没有影响。

7.7.1 案例-查找2到1000中的所有素数

找出并输出从2到1000中的所有素数,一行显示10个素数,超过10个就换行显示。

这个程序可以分解为以下一些子任务:

  • 判断一个数是否是素数
  • 判断从2到1000中每个数是否是素数
  • 输出找出的素数并统计个数
  • 每输出10个素数换行

先假设我们知道怎样判断一个数是否是素数,那么显然,可以使用for循环依次遍历2到1000,只要是素数就输出。输出时统计当前行输出了几个,如果等于10个就换行输出。伪代码如下:

// number记录一行中已经显示了几个素数
for(i = 2, number = 0; i <= 1000; i++){
    测试i是否是素数;
    如果i是素数{
           输出i和空格;
           number++;
           如果number等于10
                 输出换行符
                 number = 0; 
     }      
}

判断一个数i是否是素数,可以用2,3,4直到i/2,看看这些数中是否有数可以整除i,如果有就说明i不是素数,否则i就是素数,算法伪代码如下:

声明变量isPrime来表明i是否为素数,初始值为真
for(int k = 2; k <= i / 2; k++){
    if (i % k == 0){
        设置isPrime为假;
        跳出循环;
    }
}

完整代码如下:

#include <stdio.h>

int main() {
	for(int i = 2, number = 0; i <= 1000; i++){
		int isPrime = 1;
   		for(int k = 2; k <= i / 2; k++){
   			 if (i % k == 0){
     		   isPrime = 0;
     		   break;
   			 }
		}
   		if (isPrime){
    		printf("%d ", i);
    		number++;
    		if (number == 10){
    			printf("\n");
    			number = 0;
			}
		}    
	}
	return 0;
}

程序的输出结果各列不对齐,可以将输出整数printf("%d ", i);添加对齐的控制printf("%3d ", i);

primerangeoutput

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值