我从零开始学习C语言(13)- 循环语句 PART2

继续学习,继续更~

6.4 退出循环

我们已经知道编写循环时,在循环体前(使用while语句和for语句)或之后(使用do语句)设置退出点的方法。然而,有些时候也会需要在循环中间设置退出点 ,甚至可能需要对循环设置多个退出点。 ++break 语句++可以用于有上述这些需求的循环中。

除了break还有两个相关语句,continuegoto语句。

continue语句:跳过某次迭代的部分内容,但不会跳出整个循环。

goto语句:允许程序从一条语句跳转到另一条语句。(很少用

6.4.1 break语句

break语句除了能把程序控制从switch语句中转移出来,还可以用于跳出while、do或者for循环。

若编写一个程序来测试数字n是否为素数。我们可以写for语句把n除以2到n-1之间所有数。一旦发现有约数就跳出循环。循环终止后,再用if语句确定循环是否提前终止(n不是素数)还是正常终止(n是素数)。

cpp 复制代码
#include <stdio.h>

int main(void)
{
    for (int i = 2; i < n; i++)
        if (n % i == 0)
            break;
    if (i < n)
        printf("%d is divisible by %d\n", n, i);
    else
        printf("%d is prime\n", n);
    return 0;
}

对于退出点在循环体的中间而不是循环体之前或之后的情况,break 语句特别有用。读入用户输入并且在遇到特殊输入值时终止的循环常常属于这种类型:

cpp 复制代码
#include <stdio.h>

int main(void)
{
    int n;
    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);
    }
    return 0;
}

break语句把程序控制从包含该语句的最内层的while、do、for或switch语句中转移出来。

因此,当这些语句嵌套时,break语句只能跳出一层嵌套。思考switch语句嵌套在while语句中的情况:

while (...) {

switch (...) {

...

break;

...

}

}

break语句可以把程序控制从switch语句中转移出来 ,但是却不能跳出while循环

6.4.2 continue语句

continue语句事实上无法跳出循环。但它和break相似。break语句是把程序控制转移到循环体末尾之后 ,而continue语句刚好把程序控制转移到循环体末尾之前。 break语句会使程序控制跳出循环,而++continue语句会把程序控制留在循环内++。它们另一个区别是continue不能用在switch语句,只能用在循环中。

下面例子通过读入一串数求和,说明了continue语句的简单应用。循环在读入10个非零数后循环终止。无论何时,读入数字0都执行continue语句,控制将跳过循环体剩余部分(语句sum+=i,和语句n++;)但仍然留在循环内。

cpp 复制代码
#include <stdio.h>

int main(void)
{
    int n = 0, sum = 0;
    while (n < 10) {
      scanf("%d", &i);
      if (i == 0)
        continue;
      sum += i;
      n++;
      /* continue jumps to here */
    }
    return 0;
}

若不用continue语句,上述例子可写成:

cpp 复制代码
#include <stdio.h>

int main(void)
{
    int n = 0, sum = 0;
    while (n < 10) {
      scanf("%d", &i);
      if (i != 0){
          sum += i;
          n++;
      }
    }
    return 0;
}

6.4.3 goto语句

break和continue语句都是跳转语句:它们控制程序从一个位置转移到另一个位置。但这两者都是受限制的:break语句目标是包含该语句的循环结束之后 那一点;而continue语句的目标是循环结束之前 那一点。goto语句则可以跳转到函数中任何有标号的语句处。(C99不可用goto语句绕过变长数组)

标号是放置在语句开始处的标识符:

[标号语句] 标识符:语句

一条语句可以有多个标号。goto语句自身格式如下:

[goto语句] goto 标识符;

执行语句goto L;控制会转移到标号L后面的语句上,且该语句必须和goto语句在同一函数中。

若不用C语言的break语句,可以用下面实例代码中goto语句提前退出循环:

cpp 复制代码
#include <stdio.h>

int main(void)
{
    int d, n;
    scanf("%d", &n);
    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);
    return 0;
}

goto语句偶尔还是很有用的。考虑从包含switch语句的循环中退出的问题。正如前面看到的那样,break语句不会产生期望的效果:它可以跳出switch语句,但是无法跳出循环。goto语句解决了这个问题:

while (...) {

switch (...) {

...

goto loop_done; /* break won't work here */ ...

}

}

loop_done: ...

goto语句对于++嵌套循环的退出++也是很有用的。

程序012 账簿结算

许多简单的交互式程序都是基于菜单的:它们向用户显示可供选择的命令列表;一旦用户选择了某条命令,程序就执行相应的操作,然后提示用户输入下一条命令;这个过程一直会持续到用户选择"退出"或"停止"命令。

这类程序的核心显然是循环。循环内将有语句提示用户输入命令,读命令,然后确定执行的操作:

for (; ;) {

提示用户录入命令;

读入命令;

执行命令;

}

执行这个命令将需要switch语句(或者级联式if语句):

for (; ;) {

提示用户录入命令;

读入命令;

switch(命令){

case 命令1:执行操作1; break;

case 命令2:执行操作2; break; .

.

.

case 命令n:执行操作n; break;

default: 显示错误消息; break;

}

}

为了说明这种格式,开发一个程序用来维护账簿的余额。程序将为用户提供选择菜单:清空账户余额,往账户上存钱,从账户上取钱,显示当前余额,退出程序。选择项分别用整数0、1、2、3和4表示。程序会话类似这样:

*** ACME checkbook-balancing program ***

Commands: 0=clear, 1=credit, 2=debit, 3=balance, 4=exit

Enter command: 1

Enter amount of credit: 1042.56

Enter command: 2

Enter amount of debit: 133.79

Enter command: 1

Enter amount of credit: 1754.32

Enter command: 2

Enter amount of debit: 1400

Enter command: 2

Enter amount of debit: 68

Enter command: 2

Enter amount of debit: 50

Enter command: 3

Current balance: $1145.09

Enter command: 4

当用户录入命令4(退出)时,程序需要从switch语句以及 外围循环中退出。break语句不可能做到,同时我们又不想用goto语句。因此可以采用return语句 ,这条语句++可以使程序提前终止并返回操作系统++。

checking.c

cpp 复制代码
/* Balances a checkbook */
#include <stdio.h>
int main(void)
{
  int cmd;
  float balance = 0.0f, credit, debit;
  printf("*** ACME checkbook-balancing program ***\n");
  printf("Commands: 0=clear, 1=credit, 2=debit, ");
  printf("3=balance, 4=exit\n\n");
  for (;;) {
    printf("Enter command: ");
    scanf("%d", &cmd);
    switch (cmd) {
      case 0:
        balance = 0.0f;
        break;
      case 1:
        printf("Enter amount of credit: ");
        scanf("%f", &credit);
        balance += credit;
        break;
      case 2:
        printf("Enter amount of debit: ");
        scanf("%f", &debit);
        balance -= debit;
        break;
      case 3:
        printf("Current balance: $%.2f\n", balance);
        break;
      case 4:
        return 0;
      default:
        printf("Commands: 0=clear, 1=credit, 2=debit, ");        
        printf("3=balance, 4=exit\n\n");
        break;
    }
  }
}

注意,return语句后面没有break语句。紧跟在return语句后的break语句永远不会执行,许多编译器还将显示警告消息。

6.5 空语句

语句可以为,即除了末尾处的分号以外什么符号都没有。如下例所示:

i = 0;; j = 1;

中间那条就是空语句。

空语句主要有一个好处:编写空循环体的循环。正如6.4节中寻找素数的循环:

for (d = 2; d < n; d++) if (n % d == 0) break;

如果把条件n % d == 0移到循环控制表达式中,那么循环体就会变为空:

cpp 复制代码
for (d = 2; d < n && n % d != 0; d++)  /* empty loop body */;

每次执行循环时,先判定条件d < n。如果结果为假,循环终止;否则,判定条件 n % d ! = 0,如果结果为假则终止循环。(在后一种情况下,n % d == 0 一定为真;换句话说,找到了n的一个约数。)

注意上面是如何把空语句单独放置在一行的,不要写成

cpp 复制代码
for (d = 2; d < n && n % d != 0; d++);

C程序员习惯性地把空语句单独放置在一行。否则,一些人阅读程序时可能会混淆for语句后边的语句是否是其循环体:

cpp 复制代码
for (d = 2; d < n && n % d != 0; d++);
if (d < n)
  printf("%d is divisible by %d\n", n, d);

把普通循环转化成带空循环体的循环不会带来很大的好处:新循环往往更简洁,但通常不会提高效率。但是在一些情况下,带空循环体的循环比其他循环更高效。例如,带空循环体的循环更便于读取字符数据。

注意:不小心在if、while或for语句的圆括号后放置分号会创建空语句,从而造成if、while或for语句提前结束。

① if语句中,如果在圆括号后边放置分号,无论控制表达式的值是什么,if语句执行的动作显然都是一样的:

cpp 复制代码
if (d == 0);                                 /*** WRONG ***/
printf("Error: Division by zero\n");

因为printf函数调用不在if语句内,所以无论d的值是否等于0,都会执行此函数调用。

② while语句中,如果在圆括号后边放置分号,会产生无限循环;

③ 另一种可能是循环终止,但是在循环终止后只执行一次循环体语句;

④ for语句中,在圆括号后边放置分号会导致只执行一次循环体语句。

练习题

6.1节

  1. 下列程序片段的输出是什么?
cpp 复制代码
i = 1;
while (i <= 128){
    printf("%d ", i);  
    i *= 2;
}

推算:会输出1 2 4 8 16 32 64 128;

代码验证:

源代码:

cpp 复制代码
#include <stdio.h>

int main(void)
{
	int i = 1;
	while (i <= 128){
	    printf("%d ", i);  
	    i *= 2;
	}
	return 0;
}

6.2节

  1. 下列程序片段的输出是什么?
cpp 复制代码
i = 9384;
do {
    printf("%d ", i);  
    i /= 10;
} while (i > 0);

推算:当大于0时,缩小十倍并输出,由于是do-while循环,先输出初始值。再计算输出。

输出:9384 938 93 9

代码验证:

源代码:

cpp 复制代码
#include <stdio.h>

int main(void)
{
	int i = 9384;
	do {
	    printf("%d ", i);  
	    i /= 10;
	} while (i > 0);
	return 0;
}

6.3节

*3. 下面这条for语句的输出是什么?

cpp 复制代码
for (i = 5, j = i - 1; i > 0, j > 0; --i , j = i - 1)  
    printf("%d ", i);

分析:i初始化为5,j初始化为5 - 1 = 4,循环条件(i > 0, j > 0)扔掉i>0,逗号运算符返回j>0的结果。(--i, j = i - 1),逗号运算符左结合性,先i=i-1,再j=i-1。

输出:5 4 3 2

代码验证:

源代码:

cpp 复制代码
#include <stdio.h>

int main(void)
{
	int i, j;
	for (i = 5, j = i - 1; i > 0, j > 0; --i , j = i - 1)  
    	printf("%d ", i);
	return 0;
}
  1. 下列哪条语句和其他两条语句不等价(假设循环体都是一样的)?

(a) for (i = 0; i < 10; i++)...

(b) for (i = 0; i < 10; ++i)...

(c) for (i = 0; i++ < 10; )...

c和a、b不等价。

c等价于while循环

cpp 复制代码
int i = 0;
while(i < 10)
{
    i = i + 1;
    //dosomething...
}

a、b等价于循环

cpp 复制代码
int i = 0;
while(i < 10)
{
    //dosomething...
    i = i + 1;
}

代码验证:

cpp 复制代码
#include <stdio.h>

int main(void)
{
	for (int i = 0; i < 10; i++ )
	{
		printf("%d ", i); 
	}
	printf("\n");
	
	for (int i = 0; i < 10; ++i )
	{
		printf("%d ", i); 
	}
	printf("\n");
	
	for (int i = 0; i++ < 10; )
	{
		printf("%d ", i); 
	}
	printf("\n");
	
	return 0;
}
  1. 下列哪条语句和其他两条语句不等价(假设循环体都是一样的)?

(a) while (i < 10) {...}

(b) for (; i < 10;) {...}

(c) do {...} while (i < 10);

c和a、b不等价。

c是do...while,不管循环条件如何,都会至少执行一次循环体。

  1. 把练习题1中的程序片段改写为一条for语句。
cpp 复制代码
for(i = 1; i <= 128; i *= 2){
    printf("%d ", i);  
}
  1. 把练习题2中的程序片段改写为一条for语句。
cpp 复制代码
for(i = 9384, printf("%d ", i), i /= 10; i > 0; i /= 10)
{
    printf("%d ", i);  
    i /= 10;
}

*8. 下面这条for语句的输出是什么?

for (i = 10; i >= 1; i /= 2)

printf("%d ", i++);

输出:10 5 3 2 1 1 1 1 1 1...(1)

  1. 把练习题8中的for语句改写为一条等价的while语句。除了while循环本身之外,还需要一条语句。
cpp 复制代码
i = 10;
while(i >= 1)
{
    printf("%d ", i++);
    i /= 2;
}  
       
    

6.4节

  1. 说明如何用等价的goto语句替换continue语句。

核心思路是:‌goto跳转到循环的条件判断位置‌,跳过当前迭代剩余代码。

cpp 复制代码
// 原代码(带continue)
while (condition) {
    // 代码A
    if (skip_condition) {
        continue;  // 跳过本次迭代
    }
    // 代码B
}

// 等价goto版本
while (condition) {
    // 代码A
    if (skip_condition) {
        goto CONTINUE_LABEL;  // 跳转到循环末尾
    }
    // 代码B
    CONTINUE_LABEL: ;  // 空语句(仅作跳转目标)
}
  1. 下列程序片段的输出是什么?
cpp 复制代码
sum = 0;
for (i = 0; i < 10; i++) {
  if (i % 2)
    continue;
  sum += i;
}
printf("%d\n", sum);

推测:0 - 9 偶数之和

输出:20

代码验证:

cpp 复制代码
#include <stdio.h>
int main(void)
{
	int sum = 0;
	for (int i = 0; i < 10; i++) {
	  if (i % 2)
	    continue;
	  sum += i;
	}
	printf("%d\n", sum);
	return 0;
}
  1. 下面的"素数判定"循环作为示例出现在6.4节中:
cpp 复制代码
for (d = 2; d < n; d++)  
    if (n % d == 0)
        break;

这个循环不是很高效。没有必要用n 除以2~n-1 的所有数来判断它是否为素数。事实上,只需要检查不大于n的平方根的除数。利用这一点来修改循环。提示:不要试图计算出n的平方根,用d*d和n 进行比较

cpp 复制代码
for (d = 2; d * d <= n; d++)  
    if (n % d == 0)
        break;

6.5节

*13. 重写下面的循环,使其循环体为空。

cpp 复制代码
for (n = 0; m > 0; n++) 
    m /= 2;
cpp 复制代码
for (n = 0; m > 0; n++, m /= 2) ;

*14. 找出下面程序片段中的错误并修正。

cpp 复制代码
if (n % 2 ==0);
   printf("n is even\n");

if括号后不能有分号。

编程题

  1. 编写程序,找出用户输入的一串数中的最大数。程序需要提示用户一个一个地输入数。当用户输入0或负数时,程序必须显示出已输入的最大非负数:

Enter a number: 60

Enter a number: 38.3

Enter a number: 4.89

Enter a number: 100.62

Enter a number: 75.2295

Enter a number: 0

The largest number entered was 100.62

注意,输入的数不一定是整数。

分析:用打擂台法求最大数。

打擂台法是一种直观高效的求最大值算法,其核心思想源于"擂台比武"的比喻:我们将每个元素视为一个挑战者,让它们依次上台与当前的"擂主"(当前最大值)进行较量,胜者成为新擂主。

cpp 复制代码
#include <stdio.h>
int main(void)
{
	float n, max = 0.0f;
	do{
		printf("Enter a number: ");
		scanf("%f", &n);
		if(n > max) 
			max = n;
	}while(n > 1E-7);
	printf("The largest number entered was %g", max);
	return 0;
}
  1. 编写程序,要求用户输入两个整数,然后计算并显示这两个整数的最大公约数(GCD):

Enter two integers: 12 28

Greatest common divisor: 4

提示:求最大公约数的经典算法是Euclid算法,方法如下:

分别让变量m和n存储两个数的值。如果n为0,那么停止操作,m中的值是GCD;否则计算m除以n的余数,把n保存到m中,并把余数保存到n中。然后重复上述过程,每次都先判定n是否为0。

cpp 复制代码
#include <stdio.h>
int main(void)
{
	int m, n, r;
	printf("Enter two integers: ");
	scanf("%d %d", &m, &n);
	while(n != 0)
	{
		r = m % n;
		m = n;
		n = r;
	}
	printf("Greatest common divisor: %d", m);
	return 0;
}
  1. 编写程序,要求用户输入一个分数,然后将其约分为最简分式:

Enter a fraction: 6/12

In lowest terms: 1/2

提示:为了把分数约分为最简分式,首先计算分子和分母的最大公约数,然后分子和分母都除以最大公约数。

cpp 复制代码
#include <stdio.h>
int main(void)
{
	int a, b;
	int m, n, r;
	
	printf("Enter a fraction: ");
	scanf("%d / %d", &a, &b);
	
	m = a;
	n = b;
	while(n != 0)
	{
		r = m % n;
		m = n;
		n = r;
	}
	
	printf("In lowest terms: %d/%d", a/m, b/m);
	return 0;
}
  1. 在5.2节的broker.c程序中添加循环,以便用户可以输入多笔交易并且程序可以计算每次的佣金。程序在用户输入的交易额为0时终止。

Enter value of trade: 30000

Commission: $166.00

Enter value of trade: 20000

Commission: $144.00

Enter value of trade: 0

cpp 复制代码
/* Calculates a broker's commission */
#include <stdio.h>
int main(void)
{
 float commission, value;
 do{
	 printf("Enter value of trade: ");
	 scanf("%f", &value);
	 if(value < 1e-7) break;
	 
	 if (value < 2500.00f)
	 	commission = 30.00f + .017f * value;
	 else if (value < 6250.00f)
	 	commission = 56.00f + .0066f * value;
	 else if (value < 20000.00f)
	 	commission = 76.00f + .0034f * value;
	 else if (value < 50000.00f)
	 	commission = 100.00f + .0022f * value;
	 else if (value < 500000.00f)
	 	commission = 155.00f + .0011f * value;
	 else
	 	commission = 255.00f + .0009f * value;
	 if (commission < 39.00f)
	 	commission = 39.00f;
	 printf("Commission: $%.2f\n", commission);
  }while(1);
 return 0;
}

*5. 第4章编程题1要求编写程序显示出两位数的逆序。设计一个更具一般性的程序,可以处理一位、两位、三位或者更多位的数。**提示:**使用do循环将输入的数重复除以10,直到值达到0为止。

cpp 复制代码
#include <stdio.h>
 
int main()
{
	int n, a = 0;
	printf("Enter a number: ");
	scanf("%d", &n);
	do
	{
		a = a * 10 + n % 10;
		n /= 10;
	}while(n > 0);
	printf("The reversal is: %d", a);
	
	return 0;
}
  1. 编写程序,提示用户输入一个数,然后显示出1~n的所有偶数平方值。例如,如果用户输入100,那么程序应该显示出下列内容:

4

16

36

64

100

cpp 复制代码
#include <stdio.h>
 
int main()
{
	int n, i;
	scanf("%d", &n);
	for(i = 2; i * i <= n; i+=2)
	{
		printf("%d\n", i * i);
	}
	return 0;
}

*7. 重新安排程序square3.c,在for循环中对变量i进行初始化、判定以及自增操作。不需要重写程序,特别是不要使用任何乘法。

cpp 复制代码
/* Prints a table of squares using an odd method */
#include <stdio.h>
int main(void)
{
     int i, n, odd, square;
     printf("This program prints a table of squares.\n");
     printf("Enter number of entries in table: ");
     scanf("%d", &n);
     odd = 3;
     for (i = 1, square = 1; i <= n; odd += 2) {
         printf("%10d%10d\n", i++, square);
         square += odd;
     }
     return 0;
}
  1. 编写程序显示单月的日历。用户指定这个月的天数和该月起始日是星期几:

Enter number of days in month: 31

Enter starting day of the week (1=Sun, 7=Sat): 3

1 2 3 4 5

6 7 8 9 10 11 12

13 14 15 16 17 18 19

20 21 22 23 24 25 26

27 28 29 30 31

提示:此程序不像看上去那么难。最重要的部分是一个使用变量 i 从1计数到n 的for 语句(这里n 是此月的天数),for 语句中需要显示i的每个值。在循环中,用if 语句判定i是否是一个星期的最后一天,如果是,就显示一个换行符。

cpp 复制代码
#include <stdio.h>
 
int main()
{
	int i, n, s;
	printf("Enter number of days in month: ");
	scanf("%d", &n);
	printf("Enter starting day of the week (1=Sun, 7=Sat): ");
	scanf("%d", &s);
	
	for(i = 1; i <= s - 1; i++)
	{
		printf("   ");
	}
	
	for(i = 1; i <= n; i++)
	{
		
		if((s-2+i) % 7 == 0) printf("\n");
		printf("%2d ", i);
	}
	return 0;
}

9.第2章的编程题8要求编程计算第一、第二、第三个月还贷后剩余的贷款金额。修改该程序,要求用户输入还贷的次数并显示每次还贷后剩余的贷款金额。

10.第5章的编程题9要求编写程序判断哪个日期更早。泛化该程序,使用户可以输入任意个日期。用0/0/0指示输入结束,不再输入日期。

Enter a date (mm/dd/yy): 3/6/08

Enter a date (mm/dd/yy): 5/17/07

Enter a date (mm/dd/yy): 6/3/07

Enter a date (mm/dd/yy): 0/0/0

5/17/07 is the earliest date

  1. 数学常量 e 的值可以用一个无穷级数表示:

编写程序,用下面的公式计算e的近似值:

这里n是用户输入的整数。

  1. 修改编程题11,使得程序持续执行加法运算,直到当前项小于为止,其中是用户输入的较小的(浮点)数。

下一章是 第7章 基本类型

相关推荐
Insist75342 分钟前
k8s----学习站点搭建
学习
月盈缺1 小时前
学习嵌入式第二十三天——数据结构——栈
数据结构·学习
mysla1 小时前
嵌入式学习day34-网络-tcp/udp
服务器·网络·学习
文火冰糖的硅基工坊1 小时前
[激光原理与应用-317]:光学设计 - Solidworks - 草图
开发语言·python·信息可视化·系统架构
Moonnnn.1 小时前
【51单片机学习】AT24C02(I2C)、DS18B20(单总线)、LCD1602(液晶显示屏)
笔记·单片机·学习·51单片机
草莓熊Lotso1 小时前
【C语言强化训练16天】--从基础到进阶的蜕变之旅:Day10
c语言·开发语言·经验分享·算法·强化
草明2 小时前
docker stats 增加一列容器名称的显示
java·开发语言·docker
He1955012 小时前
Go初级二
开发语言·后端·golang
牛奶咖啡133 小时前
学习设计模式《二十三》——桥接模式
学习·设计模式·桥接模式·认识桥接模式·桥接模式的优点·何时选用桥接模式·桥接模式的使用示例
草莓熊Lotso3 小时前
【C++】--函数参数传递:传值与传引用的深度解析
c语言·开发语言·c++·其他·算法