【第六章·循环控制结构】第五节:流程的转移控制

news/2024/11/7 18:57:42/

目录

goto 语句

break 语句

示例:使用 goto 和 break 实现读入正整数程序,遇负数终止

goto 语句编程实现

break 语句编程实现

break 语句goto 语句的区别

continue 语句

break 语句continue 语句的区别

示例:使用 contiune 实现读入正整数程序,遇负数跳过

continue 对 for 转 while 循环的影响

跳出多重循环

使用 goto 语句跳出多重循环

使用 break 语句跳出多重循环 

对比总结

穷举法编程实例

示例:韩信点兵问题

问题求解方法分析

简单的程序实现

使用 break 优化程序

使用 exit() 函数优化程序

使用标志变量优化程序 

使用 do-while 语句重写程序


goto 语句

        goto 语句break 语句continue 语句return 语句C 语言中用于控制流程转移的跳转语句。其中,控制从函数返回值的 return 语句将在第 7 章介绍。

        goto 语句无条件转向语句,它既可以向下跳转,也可往回跳转。其一般形式为:

        它的作用是在不需要任何条件的情况下直接使程序跳转到该语句标号(Label)所标识的语句去执行

        其中语句标号代表 goto 语句转向的目标位置,应使用合法的标识符表示语句标号,其命名规则与变量名相同

        尽管 goto 语句是无条件转向语句,但通常情况下 goto 语句与 if 语句联合使用。其形式为:

        良好的编程风格建议少用和慎用 goto 语句,尤其是不要使用往回跳转的 goto 语句,不要让 goto 制造出永远不会被执行的代码(即死代码)。


break 语句

        break 语句除用于退出 switch 结构外,还可用于由 while、do-while 和 for 构成的循环语句的循环体中

        当执行循环体遇到 break 语句时,循环将立即终止,从循环语句后的第一条语句开始继续执行【结束本次循环】

        break 语句对循环执行过程的影响示意如下:

        可见,break 语句实际是一种有条件的跳转语句,跳转的语句位置限定为紧接着循环语句后的第一条语句。若希望跳转的位置就是循环语句后的语句,则可以用 break 语句代替 goto 语句。 

示例:使用 goto 和 break 实现读入正整数程序,遇负数终止

        【例 6.11】读入 5 个正整数并且显示它们。当读入的数据为负数时,程序立即终止。

        这个程序既可用 goto 语句来编程,也可用 break 语句来编程。

goto 语句编程实现

#include <stdio.h>int main(void)
{int i, n; // 声明循环计数器 i 和用于存储用户输入的整数 n// 使用 for 循环来尝试读取用户输入的 5 个正整数for (i = 1; i <= 5; i++){printf("Please enter n: ");scanf("%d", &n);// 如果输入的整数是负数,则跳转到 END 标签处执行代码if (n < 0)goto END;// 如果输入的整数是非负数,则打印出来printf("n = %d\n", n);}// END标签:这里是 goto 语句跳转到的位置// 无论循环是否因为遇到负数而跳出,都会执行到这里
END:printf("Program is over!\n");return 0;
}

        程序的 2 次测试结果如下:

break 语句编程实现

#include <stdio.h>int main(void)
{int i, n; // 声明循环计数器 i 和用于存储用户输入的变量 n// 使用 for 循环尝试读取 5 次用户输入for (i = 1; i <= 5; i++){printf("Please enter n: ");scanf("%d", &n);// 检查 n 是否为负数if (n < 0)// 如果是负数,则跳出循环break;// 如果 n 是非负数,则打印出来printf("n = %d\n", n);}// 无论循环是否因为达到 5 次或遇到负数而结束,都打印程序结束信息printf("Program is over!\n");return 0;
}

        程序的 2 次测试结果如下:

break 语句goto 语句的区别

        虽然 break 语句goto 语句都可用于终止整个循环的执行,但二者的本质区别在于:

  • goto 语句可以向任意方向跳转,可以控制流程跳转到程序中任意指定的语句位置;
  • break 语句只限定流程跳转到循环语句之后的第一条语句去执行,无须像 goto 语句那样用语句标号指示跳转的语句位置,因此也就避免了因过多使用 goto 语句标号使流程随意跳转而导致的程序流程混乱的问题。 

continue 语句

        continue 语句break 语句都可用于对循环进行内部控制,但二者对流程的控制效果是不同的。

        当在循环体中遇到 continue 语句时,程序将跳过 continue 语句后面尚未执行的语句,开始下一次循环,即只结束本次循环的执行,并不终止整个循环的执行【跳过本次循环】

        continue 语句对循环执行过程的影响示意如下:

break 语句continue 语句的区别

        break 语句continue 语句在流程控制上的区别可从图 6-8 和图 6-9 的对比中略见一斑。 

        break 用于立即终止当前的循环(for、while、do-while)或 switch 语句,并跳出循环体,执行循环之后的代码。

        continue 用于跳过当前循环的剩余部分,并立即开始下一次循环迭代。它不会终止整个循环,只是跳过当前迭代中 continue 之后的代码。 

示例:使用 contiune 实现读入正整数程序,遇负数跳过

        【例 6.12】continue 语句代替例 6.11 程序中的 break 语句,重新编写和运行程序,分析程序功能有什么变化,进而对 continue 与 break 语句进行对比。

#include <stdio.h>int main(void)
{int i, n; // 声明循环计数器 i 和用于存储用户输入的变量 n// 使用 for 循环尝试读取 5 次用户输入for (i = 1; i <= 5; i++){printf("Please enter n: ");scanf("%d", &n);// 检查 n 是否为负数if (n < 0)// 如果是负数,则跳过本次循环// 跳过 continue 语句后面尚未执行的语句,开始下一次循环continue;// 如果 n 是非负数,则打印出来// 如果 n 是负数,这句代码不会被执行printf("n = %d\n", n);}// 无论循环是否因为达到 5 次或遇到负数而结束,都打印程序结束信息printf("Program is over!\n");return 0;
}

        程序的运行结果如下所示:

        从上述测试结果可以看出:当程序读入正数时,显示该数;而当程序读入负数时,程序不显示该数,继续等待用户输入下一个数,直到读完 5 个数据为止。 

continue 对 for 转 while 循环的影响

        大多数 for 循环可以转换为 while 循环,但并非全部例如,当循环体中有 continue 语句时,二者就并非是等价的

        【思考题】如果将例 6.12 程序中的 for 语句用 while 语句代替,那么程序的输出结果会和原来一样吗?

        如果将例 6.12 程序中的 for 循环用 while 循环代替,并且不正确地处理循环计数器的更新,那么程序的输出结果将会与原来的不同。关键在于 for 循环中自动处理了循环计数器 i 的初始化和更新(i = 1 和 i++),而在 while 循环中,需要手动管理这些操作,并需特别注意 continue 语句可能导致的循环计数器更新被跳过的情况

        下面是一个尝试将 for 循环转换为 while 循环但不正确的例子,由于缺少对循环变量 i 的更新,它会导致循环无法结束:

#include <stdio.h>int main(void)
{int i = 1, n; // 声明并初始化循环计数器 i 和用于存储用户输入的变量 n// 使用 while 循环尝试读取用户输入while (i <= 5) // 正确的循环条件{printf("Please enter n: ");scanf("%d", &n);// 检查 n 是否为负数if (n < 0)// 如果是负数,则跳过本次循环的剩余部分continue;// 如果 n 是非负数,则打印出来printf("n = %d\n", n);// 缺少对循环变量 i 的更新!这将导致循环无法结束!}// 这条消息永远不会被打印,因为上面的循环无法结束printf("Program is over!\n");return 0;
}

        程序的运行结果如下所示:

        现在我们在加上对循环变量 i 的更新,修改程序如下所示:

#include <stdio.h>int main(void)
{int i = 1, n; // 声明并初始化循环计数器 i 和用于存储用户输入的变量 n// 使用 while 循环尝试读取用户输入while (i <= 5) // 正确的循环条件{printf("Please enter n: ");scanf("%d", &n);// 检查 n 是否为负数if (n < 0)// 如果是负数,则跳过本次循环的剩余部分continue;// 如果 n 是非负数,则打印出来printf("n = %d\n", n);// 更新循环计数器 i// 但是,需要注意的是它被放在了 continue 后面,// 如果用户输入了负数,此处的循环计数器 i 会被跳过,不会执行,即不会计数i++;}printf("Program is over!\n");return 0;
}

        程序的 2 次测试结果如下:

        上述修改后的 while 循环版本中,i 的递增操作被放在了 continue 语句之后。这种情况下,如果用户输入了负数,continue 语句会被执行,导致 i 的递增操作被跳过,从而使循环条件 i <= 5 永远不满足,形成无限循环。

        为了避免无限循环,需要确保 i 的递增操作在 continue 语句之前执行。这样无论用户输入什么值,i 都会被正确递增,从而保证循环能够正常结束。

        再次进行修改程序,如下所示:

#include <stdio.h>int main(void)
{int i = 1, n; // 声明并初始化循环计数器 i 和用于存储用户输入的变量 n// 使用 while 循环尝试读取用户输入while (i <= 5) // 正确的循环条件{printf("Please enter n: ");scanf("%d", &n);// 检查 n 是否为负数if (n < 0){// 如果是负数,则跳过本次循环的剩余部分i++; // 在 continue 之前递增 i,非常重要!!!continue;}// 如果 n 是非负数,则打印出来printf("n = %d\n", n);// 更新循环计数器 ii++; // 不要忘了,n 为非负数时,也需要递增 i}printf("Program is over!\n");return 0;
}

        程序的运行结果如下所示:


跳出多重循环

        注意,在嵌套循环的情况下,break 语句continue 语句只对包含它们的最内层的循环语句起作用,不能用 break 语句跳出多重循环若要跳出多重循环,使用 break 语句只能一层一层地跳出。而显然 goto 语句是跳出多重循环的一条捷径

        在程序设计语言中保留 goto 语句的主要原因是在某些情况下,使用 goto 语句可以提高程序的执行效率,或者使程序结构更清晰。如下两种情形特别适合于使用 goto 语句

  • 快速跳出多重循环
  • 跳向共同的出口位置,进行退出前的错误处理工作

使用 goto 语句跳出多重循环

        下面是一个简单的示例,演示如何使用 goto 语句来快速跳出多重循环,并进行退出前的错误处理工作。

#include <stdio.h>int main(void)
{int outer, inner, value;// 外层循环for (outer = 1; outer <= 3; outer++){// 内层循环for (inner = 1; inner <= 3; inner++){printf("请输入一个数字(负数退出): ");scanf("%d", &value);// 如果输入的是负数,使用 goto 语句跳出所有循环if (value < 0){goto exit_loops;}// 处理输入的值printf("正在处理数值: %d\n", value);}}exit_loops:// 共同的出口位置,进行退出前的错误处理工作printf("由于输入负数,退出所有循环。\n");// 进行一些清理工作printf("正在进行清理工作...\n");return 0;
}

        程序的运行结果如下所示: 

使用 break 语句跳出多重循环 

        下面是一个简单的示例,演示如何使用 break 语句来跳出多重循环。

#include <stdio.h>int main(void)
{int outer, inner, value;int should_exit = 0; // 用于控制跳出所有循环的标志变量// 外层循环for (outer = 1; outer <= 3; outer++){// 如果 should_exit 为 1,则跳出外层循环if (should_exit){break; // 最近的封闭循环——这里是外层循环}// 内层循环for (inner = 1; inner <= 3; inner++){printf("请输入一个数字(负数退出): ");scanf("%d", &value);// 如果输入的是负数,设置 should_exit 为 1 并跳出内层循环if (value < 0){should_exit = 1;break;  // 只能跳出最近的封闭循环——这里是内层循环}// 处理输入的值printf("正在处理数值: %d\n", value);}}// 共同的出口位置,进行退出前的错误处理工作printf("由于输入负数,退出所有循环。\n");// 进行一些清理工作printf("正在进行清理工作...\n");return 0;
}

        程序的运行结果如下所示:

对比总结

        使用 goto 语句:可以直接跳出多重嵌套的循环,代码简洁,但可能会降低代码的可读性。

        使用 break 语句需要在每一层循环中设置标志变量,并在外层循环中检查该标志变量,代码相对冗长,但可读性较好。 


穷举法编程实例

示例:韩信点兵问题

        【例 6.13】韩信点兵问题。韩信有一队兵,他想知道有多少人,便让士兵排队报数。按从 1 至 5 报数,最末一个士兵报的数为 1;按从 1 至 6 报数,最末一个士兵报的数为 5;按从 1 至 7 报数,最末一个士兵报的数为 4;最后再按从 1 至 11 报数,最末一个士兵报的数为 10。请编程计算韩信至少有多少兵?

问题求解方法分析

        设兵数为 x,则按题意 x 应满足下述关系式:

x % 5 == 1 && x % 6 == 5 && x % 7 == 4 && x % 11 == 10

        采用穷举法对 x 从 1 开始逐个试验,第一个使得上述关系式成立的 x 值即为所求。

        所谓穷举(Exhaustion),简单地说就是通过尝试问题的所有可能来得到最终答案

简单的程序实现

#include <stdio.h>int main(void)
{int x;// 从 1 -3000 开始穷举遍历,不严谨for (x = 1; x < 3000; x++){if (x % 5 == 1 && x % 6 == 5 && x % 7 == 4 && x % 11 == 10){printf("x = %d\n", x);}}return 0;
}

        程序的运行结果如下所示:

        虽然这个程序似乎也得到了正确的结果,但这实属偶然,算是“瞎猫碰到了死耗子”,因为程序只从 1 试验到 3000,如果真正的解不在这个范围内,那么程序运行以后将一无所获。

使用 break 优化程序

        如果去掉 x < 3000 的限制,又会怎样呢?

        显然一定会找到解,而且不止一个解,更严重的是程序将陷入死循环,即循环永远都不会退出

        那么如何让循环在找到第一个满足关系式的解后立即退出循环呢?

        显然,可以使用 break 语句,程序如下:

#include <stdio.h>int main(void)
{int x;for (x = 1;; x++) // 去掉 x < 3000 的限制{if (x % 5 == 1 && x % 6 == 5 && x % 7 == 4 && x % 11 == 10){printf("x = %d\n", x);break; // 找到第一个满足关系式的解后立即退出循环}}return 0;
}

使用 exit() 函数优化程序

        由于本例中,程序退出循环以后什么也不做,直接结束程序的运行,因此还可以调用函数 exit() 来直接结束程序的运行。程序如下:

#include <stdio.h>
#include <stdlib.h> // 使用 exit() 函数int main(void)
{int x;for (x = 1;; x++) // 去掉 x < 3000 的限制{if (x % 5 == 1 && x % 6 == 5 && x % 7 == 4 && x % 11 == 10){printf("x = %d\n", x);exit(0); // 找到第一个满足关系式的解后立即退出循环}}return 0;
}

使用标志变量优化程序 

        可读性更好的方法是使用标志变量,即定义一个标志变量 find,标志是否找到了解,先置 find 为假,表示 “未找到”,一旦找到了满足给定条件的解,就将 find 置为真,表示 “找到了”。

        相应的,循环控制表达式取为 “find 的逻辑非” 的值,当 find 值为 0(假)时,即 !find 的值为真,表示未找到,继续循环,否则表示已找到,退出循环。程序如下: 

#include <stdio.h>int main(void)
{int x;int find = 0; // 先置 find 为假,表示 “未找到”// find 为假时继续循环for (x = 1; !find; x++){if (x % 5 == 1 && x % 6 == 5 && x % 7 == 4 && x % 11 == 10){printf("x = %d\n", x);find = 1; // 一旦找到了满足给定条件的解,就将 find 置为真}}return 0;
}

        【思考题】第 13 行输出 x 值的语句放到 for 循环体外,还能输出正确的结果吗?

        如果将第 13 行输出 x 值的语句放到 for 循环体外,程序将无法正确输出结果。这是因为 printf 语句需要在找到符合条件的 x 值时立即执行,而不是等到循环结束后再执行。如果将 printf 语句移到循环体外,程序会在循环结束后只打印一次 x 的值,此时 x 的值将是循环结束时的值,而不是满足条件的那个值。因此,输出的结果将是不正确的。

        例如,将 printf 语句移到循环体外,修改代码如下:

#include <stdio.h>int main(void)
{int x;int find = 0; // 先置 find 为假,表示 “未找到”// find 为假时继续循环for (x = 1; !find; x++){if (x % 5 == 1 && x % 6 == 5 && x % 7 == 4 && x % 11 == 10){find = 1; // 一旦找到了满足给定条件的解,就将 find 置为真}}printf("x = %d\n", x); // 输出 x 值// 这个值将是正确值 + 1 后的结果// 因为上面找到这个数后,又对循环变量 x 进行了 ++ 操作,然后才结束循环return 0;
}

        程序的运行结果如下所示:

        结果分析:

  • 原始代码:当 x 为 2111 时,printf 语句立即执行,输出 x = 2111,然后退出循环。
  • 修改后的代码:当 x 为 2111 时,find 被置为真(1),再次执行 x++,此时 x 为 2112,由于循环条件不再满足,所以循环退出。此时 x 的值已经不再是 2111,而是循环结束时的值 2112。

使用 do-while 语句重写程序

        上面这个程序还可以用 do-while 语句来重写,使程序更简洁,可读性更好。

#include <stdio.h>int main(void)
{int x = 0;    // 因 do-while 循环中先对 x 加 1,故这里 x 初始化为 0int find = 0; // 标志变量初值置为假do{x++;find = (x % 5 == 1 && x % 6 == 5 && x % 7 == 4 && x % 11 == 10);} while (!find);printf("x = %d\n", x);return 0;
}

        程序第 11 行语句根据逻辑表达式 x % 5 == 1 && x % 6 == 5 && x % 7 == 4 && x % 11 == 10 的值为真还是为假,相应地将标志变量 find 置为真或假。

        当然,上述程序也可以写成如下形式:

#include <stdio.h>int main(void)
{int x = 0; // 因 do-while 循环中先对 x 加 1,故这里 x 初始化为 0do{x++;} while (!(x % 5 == 1 && x % 6 == 5 && x % 7 == 4 && x % 11 == 10));printf("x = %d\n", x);return 0;
}

        在这个程序中,我们没有使用额外的标志变量,而是直接在 while 循环的条件中检查了逻辑表达式。这里,!(x % 5 == 1 && x % 6 == 5 && x % 7 == 4 && x % 11 == 10) 相当于第一个程序中的 !find。当 x 不满足条件时,逻辑表达式的结果为 true(非零值),因此循环继续。一旦 x 满足条件,逻辑表达式的结果变为 false(零值),循环终止。      

        两种方法的原理都是相同的:它们都通过循环不断增加 x 的值,直到找到一个满足特定条件的 x。

  • 第一个程序使用了一个额外的标志变量 find 来控制循环的结束;
  • 第二个程序则直接在循环条件中检查了该条件。

        从代码简洁性和可读性的角度来看,第二个程序更直接和简洁,因为它避免了引入额外的变量。此外,它使循环的逻辑更加清晰,因为循环的继续和终止条件直接体现在 while 语句中。


http://www.ppmy.cn/news/1545142.html

相关文章

香橙派Zero3部署Talebook电子书库结合内网穿透随时随地享受阅读乐趣

文章目录 前言1. 添加镜像源2. 安装Talebook3. 简单使用介绍4. 安装内网穿透工具5. 配置固定公网地址 前言 本文主要介绍如何在刷了CasaOS轻NAS系统的香橙派Orange Pi Zero3中&#xff0c;使用Docker本地部署Talebook电子书管理系统并结合cpolar内网穿透实现远程管理本地书籍与…

DS二叉树--赫夫曼树解码

题目描述 已知赫夫曼编码算法和程序&#xff0c;在此基础上进行赫夫曼解码 可以增加一个函数&#xff1a;int Decode(const string codestr, char txtstr[]);//输入编码串codestr&#xff0c;输出解码串txtstr 该方法如果解码成功则返回1&#xff0c;解码失败则返回-1&…

Browserslist 配置

Browserslist 是一个工具和规范&#xff0c;用于定义和共享支持的浏览器列表&#xff0c;以便在前端开发中管理不同工具的兼容性。这些工具可以包括 Babel、Autoprefixer、ESLint 等&#xff0c;它们都可以使用 Browserslist 提供的配置来确定应支持哪些浏览器及其版本。 主要…

【Springboot问题】创建springboot项目后没有Resources文件夹及application文件

问题描述&#xff1a; 在创建springboot项目之后&#xff0c;由于项目识别的问题&#xff0c;没有出现资源文件夹以及application文件。 解决方法&#xff1a; 但是此刻依旧没有application.yml文件&#xff0c;创建

服务器上删除超大文件夹的解决方案

1.示例主脚本 delete_all_folders.sh 它会遍历指定目录下的所有子目录&#xff0c;并调用 delete_files.sh 脚本删除每个子目录中的文件和空目录 #!/bin/bash# 检查是否指定了根目录路径 if [ -z "$1" ]; thenecho "Usage: $0 <root_directory>"ex…

(十四)JavaWeb后端开发——MyBatis

目录 1.MyBatis概述 2.MyBatis简单入门 3.JDBC&#xff08;了解即可&#xff09; 4.数据库连接池​ 5.lombok 6.MyBatis基本操作 7.XML映射文件 8.动态SQL 8.1 if标签 8.2 foreach标签 8.3 sql/include标签​ 1.MyBatis概述 MyBatis是一款优秀的持久层&#xff08…

大模型的常用指令格式 --> ShareGPT 和 Alpaca (以 llama-factory 里的设置为例)

ShareGPT 格式 提出背景&#xff1a;ShareGPT 格式起初来自于用户在社交平台上分享与聊天模型的对话记录&#xff0c;这些记录涵盖了丰富的多轮对话内容。研究者们意识到&#xff0c;这类真实的对话数据可以帮助模型更好地学习多轮对话的上下文保持、回应生成等能力。因此&…

有源电力滤波器为什么能用在对电能质量要求高的场所?

有源电力滤波器&#xff08;APF&#xff09;主要应用于对电能质量要求较高的场所&#xff0c;并且常与产生谐波和无功功率的设备一起配合使用。 一、有源电力滤波器应用场所&#xff1a; 1、工厂生产线&#xff1a;在自动化生产线中&#xff0c;有大量的变频器用于电机调速。…