22 程序控制语句详解:跳转控制(break、continue、goto)、死循环应用、程序控制编程实战
1 break 语句
1.1 介绍
break 语句用于立即终止当前所在的循环或 switch 语句块的执行,将程序控制权转移到该语句块之后的下一条语句。
- 循环语句:提前退出循环体(仅退出最近的封闭循环)。
- switch 语句:防止代码 "穿透" 执行下一个 case 分支。
1.2 功能流程图
1.3 在循环中使用 break
break 语句在循环中用于在满足特定条件时提前终止循环,跳过循环中剩余的语句和后续的循环迭代。
#include <stdio.h>int main()
{for (int i = 0; i < 10; i++){if (i == 3){break; // 当 i=3 时终止循环}printf("%d \t", i); // 输出: 0 1 2}return 0;
}
程序在 VS Code 中的运行结果如下所示:
1.4 嵌套循环中的 break
在嵌套循环中,break 只会终止最近的封闭循环(内层循环),外层循环不受影响。
#include <stdio.h>int main()
{for (int i = 1; i <= 3; i++){printf("外层循环:i = %d\n", i);for (int j = 1; j <= 5; j++){if (j == 4){break; // 只退出内层循环}printf(" 内层循环:j = %d\n", j);}}return 0;
}
程序在 VS Code 中的运行结果如下所示:
1.5 案例:质数判断
编写一个程序,要求用户输入一个数字,判断该数字是否是质数(又称素数,是指在大于 1 的自然数中,除了 1 和它本身以外,不能被其他自然数整除的数)。
方法一:使用布尔类型(C99 及以上)
#include <stdio.h>
#include <stdbool.h> // 引入布尔类型支持int main()
{int num;printf("请输入一个正整数:");scanf("%d", &num);// 初始化质数标志// 质数是大于 1 的自然数,且只能被 1 和它本身整除// 这里使用三元运算符来初始化 isPrime// 如果 num 小于等于 1,则不是质数,否则是质数bool isPrime = num > 1 ? true : false;// 如果 num 大于 1,则进行质数判断// 从 2 开始判断到 num 的一半// 因为一个数的因数不可能大于它的一半// 例如:6 的因数是 1, 2, 3, 6,最大因数是 3for (int i = 2; i <= num / 2; i++){if (num % i == 0){isPrime = false; // 发现因数,不是质数}}// 输出结果if (isPrime) // 如果 isPrime 为 true,则 num 是质数{printf("%d 是质数。\n", num);}else // 如果 isPrime 为 false,则 num 不是质数{printf("%d 不是质数。\n", num);}return 0;
}
方法二:使用整型标志(兼容所有 C 版本)
#include <stdio.h>int main()
{int num;printf("请输入一个正整数:");scanf("%d", &num);// 初始化质数标志(1 表示 true,0 表示 false)// 质数是大于 1 的自然数,且只能被 1 和它本身整除// 这里使用三元运算符来初始化 isPrime// 如果 num 小于等于 1,则不是质数,否则是质数int isPrime = num > 1 ? 1 : 0;// 如果 num 大于 1,则进行质数判断// 从 2 开始判断到 num 的一半// 因为一个数的因数不可能大于它的一半// 例如:6 的因数是 1, 2, 3, 6,最大因数是 3for (int i = 2; i <= num / 2; i++){if (num % i == 0){isPrime = 0; // 发现因数,不是质数}}// 输出结果if (isPrime){printf("%d 是质数。\n", num);}else{printf("%d 不是质数。\n", num);}return 0;
}
程序优化:
- 将循环上限从 num/2 优化为 sqrt(num)(通过 i*i <= num 实现)。
- 在循环条件中加入 && isPrime,使得一旦确定非质数就立即终止循环。
- 使用三元运算符简化输出语句,添加更友好的用户提示。
#include <stdio.h>int main()
{int num;printf("请输入一个正整数:");scanf("%d", &num);// 初始化质数标志(1 表示 true,0 表示 false)// 质数是大于 1 的自然数,且只能被 1 和它本身整除// 这里使用三元运算符来初始化 isPrime// 如果 num 小于等于 1,则不是质数,否则是质数int isPrime = num > 1 ? 1 : 0;// 如果 num 大于 1,则进行质数判断// 将循环上限从 num / 2 优化为 sqrt(num) (通过 i *i <= num 实现)// 因为一个数的因数不可能大于它的平方根(可能比一半要小,优化循环次数)// 在循环条件中加入 && isPrime,使得一旦确定非质数就立即终止循环for (int i = 2; i * i <= num && isPrime; i++){if (num % i == 0){isPrime = 0; // 发现因数,标记为非质数}}// 输出结果// %s 用于格式化字符串,在 printf 中使用可以输出字符串printf("%d %s质数!\n", num, isPrime ? "是" : "不是");return 0;
}
程序在 VS Code 中的多次运行结果如下所示:
提示:
- 质数判断只需检查到平方根即可,这是数学上的优化。
- %s 用于格式化字符串,在 printf 中使用可以输出字符串。
1.6 注意事项
- 使用范围限制:break 只能用于 switch 或循环语句中,在其它地方使用会导致编译错误。
- 嵌套循环处理:break 只能退出当前所在循环,多层退出需使用多个 break 或标志变量。
- switch 语句中的穿透效应:在 switch 中可以使用 break 防止执行不需要的 case 分支。
- 替代方案:对于复杂嵌套循环,可考虑使用 goto 语句(虽不推荐,但在某些场景下是合理选择)。
1.7 最佳实践
- 在简单循环中优先使用 break 提前退出。
- 对于嵌套循环,考虑重构代码或使用标志变量控制外层循环。
- 在 switch 语句中建议始终为每个 case 添加明确的 break(除非有意利用穿透特性)。
2 continue 语句
2.1 介绍
continue 是循环控制语句,只能用于 for、while 或 do-while 循环中。
它的作用是跳过当前循环迭代的剩余部分,直接进入下一次循环。
2.2 功能流程图
2.3 在循环中使用 continue
continue 语句用于控制循环流程,当满足特定条件时跳过当前迭代的剩余部分,直接进入下一次循环迭代。
- 跳过当前迭代:当 continue 被执行时,循环体中 continue 之后的代码将被忽略。
- 进入下一次迭代:立即开始下一次循环检查(对于 for 循环,会先执行迭代部分)。
#include <stdio.h>int main()
{// 示例:打印 1-10,跳过数字 5for (int i = 1; i <= 10; i++){if (i == 5){continue; // 跳过数字 5}printf("%d ", i);}return 0;
}
程序在 VS Code 中的运行结果如下所示:
2.4 嵌套循环中的 continue
在嵌套循环中,continue 只影响最内层的循环。如果需要影响外层循环,需要使用标记变量或 goto(不推荐)。
#include <stdio.h>int main()
{for (int i = 1; i <= 3; i++){printf("外层循环:i = %d\n", i);for (int j = 1; j <= 4; j++){if (j == 3){continue; // 只跳过内层循环的当前迭代}printf(" 内层循环:j = %d\n", j);}}return 0;
}
程序在 VS Code 中的运行结果如下所示:
2.5 案例:逢七过游戏
实现一个 "逢七过" 游戏,输出 1 到 100(包含 1 和 100)的数字,但需要跳过以下数字:
- 7 的倍数
- 包含数字 7 的数字(个位或十位为 7)
#include <stdio.h>int main()
{int counter = 0; // 计数器,用于记录输出的数字个数// 循环从 1 到 100for (int i = 1; i <= 100; i++){// 检查当前数字是否是 7 的倍数,个位数是否为 7,或者十位数是否为 7if (i % 7 == 0 || i % 10 == 7 || i / 10 == 7){continue; // 如果条件满足,跳过当前数字}printf("%d ", i); // 输出符合条件的数字counter++; // 增加计数器// 每 10 个数字换行if (counter % 10 == 0){printf("\n");}}return 0;
}
程序在 VS Code 中的运行结果如下所示:
2.6 注意事项
1. 与 break 的区别:
- continue:跳过当前迭代,继续下一次循环。
- break:完全终止循环。
2. 循环变量更新:
- 在 while 循环中,确保 continue 不会跳过循环变量的更新。
- 错误的 continue 放置可能导致无限循环。
- 错误示例:
#include <stdio.h>int main()
{// 错误:可能导致无限循环int i = 0;while (i < 5){if (i % 2 == 0){continue; // 跳过 i 的递增}printf("%d", i);i++;}return 0;
}
- 正确写法:
#include <stdio.h>int main()
{int i = 0;while (i < 5){if (i % 2 == 0){printf("跳过偶数: %d\n", i);i++; // 这里需要先自增,否则会陷入死循环continue;}printf("奇数: %d\n", i);i++; // 自增}return 0;
}
2.7 最佳实践
1. 明确使用场景:
- 只在确实需要跳过当前迭代时使用 continue。
- 避免滥用,特别是简单的 if-else 结构可以替代时。
2. 代码可读性:
- 复杂条件拆分为多个 if 语句:
// 不推荐
if (condition1 && condition2 || condition3) {continue;
}// 推荐
if (condition1) {if (condition2 || condition3) {continue;}
}
3. 循环变量安全:
- 在 while 循环中,确保 continue 不会跳过循环变量的更新。
- 考虑将循环变量的更新放在循环开始处。
4. 注释说明:
- 对复杂的 continue 使用添加注释,说明跳过的原因。
5. 替代方案:
- 对于简单的条件判断,考虑使用 if-else 结构替代 continue:
// 使用 continue
for (...) {if (condition) continue;// 执行代码
}// 替代方案
for (...) {if (!condition) {// 执行代码}
}
3 goto 语句
3.1 介绍
goto 语句是一个无条件跳转语句,它允许程序跳转到同一函数或同一作用域块内的指定标签(label)处继续执行。标签名称需符合标识符规范。
3.2 语法格式
goto label_name; // 跳转到指定标签
// ...
label_name: // 定义标签statement; // 跳转到这里执行
- 注意:goto 后面引用了未定义的标签会导致编译错误。
3.3 功能流程图
3.4 案例:跳过代码块
#include <stdio.h>int main()
{printf("Start \n"); // 开始// 跳转到标签 label1 处执行goto label1; // 跳过下面的两条打印语句// 以下语句由于 goto 的存在不会被执行printf("ok1 \n"); // 跳过printf("ok2 \n"); // 跳过label1:// 跳转到这里执行printf("ok3 \n"); // 执行printf("ok4 \n"); // 执行printf("End \n"); // 结束return 0;
}
程序在 VS Code 中的运行结果如下所示:
3.5 案例:条件跳过循环
#include <stdio.h>int main()
{int i = 11;// 使用 goto 跳转到循环结束标签if (i > 10){goto end_loop; // 直接跳转到循环结束标签}// 以下循环不会执行for (i = 0; i < 5; i++){printf("i = %d\n", i); // 这行不会执行}end_loop:printf("跳过上面的循环-未执行循环\n");return 0;
}
程序在 VS Code 中的运行结果如下所示:
3.6 使用 goto 实现循环
虽然不推荐,但 goto 可以用于实现循环逻辑:
#include <stdio.h>int main()
{int i = 1; // 初始化计数器start: // 定义循环起始标签if (i <= 5) // 循环条件{printf("%d ", i); // 打印当前值i++; // 递增计数器goto start; // 跳回循环起始处}return 0; // 循环结束
}
程序在 VS Code 中的运行结果如下所示:
3.7 注意事项
1. 关键限制:
- 不能跨函数跳转(不能从一个函数跳转到另一个函数的标签)。
- 不能跨多层嵌套作用域跳转。
- 只能跳转到同一函数内的标签。
2. 使用建议:
- 通常被认为是不良编程实践,可能导致代码难以理解和维护。
- 不建议在常规开发中使用。
- 需要理解其执行流程以阅读包含 goto 的代码。
3.8 最佳实践
- 避免使用:在大多数情况下,使用结构化控制流(for、while、do-while)更可取。
- 有限使用场景:
- 从深层嵌套中跳出(但通常有更好的结构化解决方案)。
- 某些特定算法实现(如状态机)。
- 代码可读性:
- 如果必须使用,添加详细注释说明原因。
- 保持标签命名清晰且有意义。
- 替代方案:
- 使用 break 和 continue 控制循环。
- 使用函数封装复杂逻辑。
- 使用标志变量控制流程。
goto 语句虽然功能强大,但会破坏程序的结构化特性,应谨慎使用。在绝大多数情况下,结构化编程技术可以提供更清晰、更易维护的解决方案。
4 死循环
4.1 介绍
死循环(Infinite Loop)是指一种无法自行终止的循环结构,程序会一直重复执行循环体内的代码,除非被外部干预(如用户中断、系统信号或程序异常)。具有以下特点:
- 循环条件始终为真。
- 不会自然退出。
- 通常需要明确的退出机制(如 break 语句)。
4.2 死循环的实现方式
使用 while 循环
while (1) {// 循环体代码// 需要 break 语句或其他方式退出
}
使用 do-while 循环
do {// 循环体代码// 需要 break 语句或其他方式退出
} while (1);
使用 for 循环
for (;;) {// 循环体代码// 需要 break 语句或其他方式退出
}
使用 goto 语句
start:// 循环体代码// 需要 break 语句或其他方式退出goto start;
4.3 退出死循环的常见方式
break 语句
while (1) {if (exit_condition) {break; // 退出循环}// 其他代码
}
return 语句
void infinite_loop() {while (1) {if (exit_condition) {return; // 退出函数}// 其他代码}
}
goto 语句
while (1) {if (exit_condition) {goto exit_loop; // 跳转到循环外}// 其他代码
}
exit_loop:
4.4 案例:命令行菜单系统
#include <stdio.h>
#include <stdlib.h>int main() {int choice;while (1) {printf("\n菜单:\n");printf("1. 选项1\n");printf("2. 选项2\n");printf("3. 退出\n");printf("请选择: ");scanf("%d", &choice);switch (choice) {case 1:printf("执行选项1\n");break;case 2:printf("执行选项2\n");break;case 3:printf("退出程序\n");return 0; // 退出程序default:printf("无效选择\n");}}
}
注意:
- 必须有退出机制
- 死循环必须提供明确的退出条件(如用户输入、信号触发或逻辑判断),否则程序将无法终止。
- 示例:while (1) { if (user_input == 'q') break; }
- 防止逻辑错误
- 确保循环条件或退出逻辑正确,避免因条件永远为真导致 “真死循环”。
- 错误示例:while (i < 10) { if (i == 5) continue; i++; }(i 停在5)
5 编程练习
5.1 水仙花数判断
编写一个 C 语言程序,该程序直接在 main 函数中实现判断一个整数是否为水仙花数的功能。水仙花数是一个三位数,其各位数字的立方和等于该数本身。例如,153 是一个水仙花数,因为 1^3 + 5^3 + 3^3 = 153。
#include <stdio.h>int main()
{int num; // 存储用户输入的整数int ones; // 存储个位数字int tens; // 存储十位数字int hundreds; // 存储百位数字int cubeSum; // 存储各位数字的立方和// 提示用户输入一个三位数printf("请输入一个整数(三位数):");scanf("%d", &num);// 检查输入的数字是否为三位数(范围:100 到 999)if (num < 100 || num > 999){printf("输入的不是一个三位数。\n");return 1; // 返回非零值表示程序异常退出}// 分解数字为个位、十位和百位ones = num % 10; // 获取个位数:num 除以 10 的余数tens = (num / 10) % 10; // 获取十位数:num 除以 10 去掉个位后,再除以 10 的余数hundreds = num / 100; // 获取百位数:num 除以 100 的整数部分// 计算各位数字的立方和cubeSum = ones * ones * ones + // 个位数的立方tens * tens * tens + // 十位数的立方hundreds * hundreds * hundreds; // 百位数的立方// 判断是否为水仙花数:立方和是否等于原始数字if (cubeSum == num){printf("%d 是水仙花数。\n", num); // 输出是水仙花数}else{printf("%d 不是水仙花数。\n", num); // 输出不是水仙花数}return 0; // 程序正常退出
}
程序在 VS Code 中的多次运行结果如下所示:
5.2 计算指定年份和月份的天数
编写一个 C 语言程序,接收用户输入的月份(1-12)和年份,计算并输出该月份在指定年份中的天数。程序需正确处理闰年情况(闰年 2 月 29 天,平年 2 月 28 天)。
#include <stdio.h>int main()
{int month, year; // 存储用户输入的月份和年份printf("请输入月份(1-12):");scanf("%d", &month);printf("请输入年份:");scanf("%d", &year);// 检查月份是否合法(1-12)if (month < 1 || month > 12){printf("输入的月份不合法。\n");return 1; // 非零返回值表示异常结束}// 初始化天数,默认为 31 天(1、3、5、7、8、10、12 月)int days = 31;// 根据月份调整天数switch (month){case 2: // 2 月:判断是否为闰年if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)){days = 29; // 闰年 2 月 29 天}else{days = 28; // 平年 2 月 28 天}break;case 4:case 6:case 9:case 11:days = 30; // 4、6、9、11 月为 30 天break;// 其他月份(1、3、5、7、8、10、12 月)保持默认的 31 天}// 输出结果printf("%d年%d月有%d天。\n", year, month, days);return 0; // 程序正常结束
}
程序在 VS Code 中的多次运行结果如下所示:
5.3 打印 1000 年到 9999 年之间的所有闰年
编写一个 C 语言程序,分别使用 for 循环、while 循环和 do-while 循环遍历 1000 年到 9999 年的所有年份,判断并打印闰年。闰年规则:能被 4 整除但不能被 100 整除,或能被 400 整除。将三种循环的实现合并到一个程序中,并优化输出格式(每 10 个年份换行)。
#include <stdio.h>int main()
{printf("使用 for 循环打印闰年:\n");int count = 0; // 计数器,每打印 10 个年份换行// 使用 for 循环遍历 1000 年到 9999 年for (int year = 1000; year <= 9999; year++){// 判断是否为闰年if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)){printf("%d\t", year); // 打印闰年count++;// 每打印 10 个年份换行if (count % 10 == 0){printf("\n");}}}printf("\n\n使用 while 循环打印闰年:\n");count = 0; // 重置计数器int year = 1000; // 初始化年份为 1000// 使用 while 循环遍历 1000 年到 9999 年while (year <= 9999){// 判断是否为闰年if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)){printf("%d\t", year); // 打印闰年count++;// 每打印 10 个年份换行if (count % 10 == 0){printf("\n");}}year++; // 年份递增}printf("\n\n使用 do-while 循环打印闰年:\n");count = 0; // 重置计数器year = 1000; // 重新初始化年份为 1000// 使用 do-while 循环遍历 1000 年到 9999 年do{// 判断是否为闰年if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)){printf("%d\t", year); // 打印闰年count++;// 每打印 10 个年份换行if (count % 10 == 0){printf("\n");}}year++; // 年份递增} while (year <= 9999); // 循环条件return 0;
}
5.4 正序和逆序打印英文字母
编写一个 C 语言程序,首先按顺序打印所有小写英文字母(a 到 z),然后以逆序打印所有大写英文字母(Z 到 A)。
#include <stdio.h>int main()
{// 打印小写字母 a 到 zprintf("小写字母 a-z:\n");for (char c = 'a'; c <= 'z'; c++){printf("%c ", c); // 打印当前字母并加空格}printf("\n"); // 换行// 打印大写字母 Z 到 Aprintf("大写字母 Z-A:\n");for (char c = 'Z'; c >= 'A'; c--){printf("%c ", c); // 打印当前字母并加空格}printf("\n"); // 换行return 0;
}
程序在 VS Code 中的运行结果如下所示:
5.5 计算交错序列的和
编写一个 C 语言程序,计算并输出序列 1 - 1/2 + 1/3 - 1/4 + ... + 1/n 的和,其中 n 是一个用户指定的正整数(例如,n = 100)。序列中的每一项的符号交替出现,即第一项为正,第二项为负,第三项为正,以此类推。
#include <stdio.h>int main()
{int n; // 用户指定的正整数 ndouble sum = 0.0; // 用来存储序列的和int sign = 1; // 用来表示当前的符号,初始化为正(1)// 提示用户输入正整数 nprintf("请输入一个正整数 n:");scanf("%d", &n); // 读取用户输入的 n// 计算交错序列的和for (int i = 1; i <= n; i++){// 将当前项加到总和中,注意将 1 转换为 1.0 以避免整数除法sum += sign * 1.0 / i;// 改变符号,为下一次迭代做准备sign = -sign;}// 输出结果printf("序列 1 - 1/2 + 1/3 - ... + 1/%d 的和为: %f\n", n, sum);return 0;
}
程序在 VS Code 中的运行结果如下所示:
5.6 回文数判断
编写一个 C 语言程序,输入一个整型数,判断其是否为回文数(Palindrome Number)。回文数是指一个数字无论从左向右读还是从右向左读都一样的数。如果是回文数,输出 yes,否则输出 no。
#include <stdio.h>int main()
{int num, reversed = 0, original; // 存储用户输入的数字、反转后的数字和原始数字// 提示用户输入一个整数printf("请输入一个整数:");scanf("%d", &num);original = num; // 保存原始数字,用于后续比较// 反转数字while (num != 0){reversed = reversed * 10 + num % 10; // 将当前数字的个位添加到反转数字中// reversed * 10 是将反转数字向左移动一位// num % 10 是获取当前数字的个位num /= 10; // 去除当前数字的个位}// 判断原始数字是否与反转后的数字相等if (original == reversed){printf("yes\n"); // 是回文数}else{printf("no\n"); // 不是回文数}return 0;
}
程序在 VS Code 中的多次运行结果如下所示:
5.7 计算 n 的阶乘
编写一个 C 语言程序,利用 for 循环计算用户输入的整数 n 的阶乘(n!),并输出结果。
#include <stdio.h>int main()
{int number, factorial = 1; // 存储用户输入的整数和阶乘结果// 提示用户输入一个整数printf("请输入一个整数:");// 读取用户输入的数scanf("%d", &number);// 使用 for 循环计算阶乘// 由于 factorial 初始值为 1,所以循环变量可以从 2 开始for (int i = 2; i <= number; i++){factorial *= i; // 累乘计算阶乘}// 输出结果printf("%d 的阶乘 %d!:%d\n", number, number, factorial);return 0;
}
程序在 VS Code 中的多次运行结果如下所示:
5.8 货币换零钱组合问题
某人想将一张面值 100 元的货币换成 10 元、5 元、2 元和 1 元面值的票子,要求换正好 40 张,并且每种票子至少各有一张。编写一个 C 语言程序,计算并输出满足条件的换法总数。
题目分析:
为了更高效地解决这个问题,我们需要考虑每种面额纸币的数量范围,并据此设置循环的边界条件。具体地,我们可以这样设定:
- 10 元纸币:由于至少要有 1 张,且最多不能超过 9 张(否则总金额会超过 100 元),所以循环范围从 1 到 9。
- 5 元纸币:在剩余金额和剩余张数(40 减去已选的10元纸币数量)的限制下,最多可以选取(100 - 10*已选10元数 - 2 - 1) / 5 张,但至少要有 1 张,所以循环范围需要根据已选的 10 元纸币数量动态计算。
- 2 元纸币:同样地,其数量也需要在剩余金额和剩余张数的限制下确定,但至少要有 1 张。
- 1 元纸币:最后,剩余的张数和金额将全部用于 1 元纸币,但也需要至少 1 张。
#include <stdio.h>int main()
{int count = 0; // 计数器,记录符合条件的组合数// 遍历 10 元纸币的可能数量(1 到 9张)for (int ten = 1; ten <= 9; ten++){// 遍历 5 元纸币的可能数量(至少 1 张,最多不超过剩余金额和张数限制)// (100 - 10 * ten - 2 - 1) / 5 表示最多可以使用的 5 元纸币数量// 这里减去 2 是因为至少需要留出 1 张 2 元纸币和 1 张 1 元纸币for (int five = 1; five <= (100 - 10 * ten - 2 - 1) / 5; five++){// 遍历 2 元纸币的可能数量(至少 1 张,最多不超过剩余金额和张数限制)// (100 - 10 * ten - 5 * five - 1) / 2 表示最多可以使用的 2 元纸币数量// 这里减去 1 是因为至少需要留出 1 张 1 元纸币for (int two = 1; two <= (100 - 10 * ten - 5 * five - 1) / 2; two++){// 计算 1 元纸币的数量int one = 40 - ten - five - two;// 检查 1 元纸币数量是否合法(至少 1 张,且金额正确)if (one >= 1 && (10 * ten + 5 * five + 2 * two + one) == 100){count++;printf("组合 %d: 10元x%d, 5元x%d, 2元x%d, 1元x%d\n",count, ten, five, two, one);}}}}printf("总共有 %d 种符合条件的换法\n", count); // 34return 0;
}
程序在 VS Code 中的运行结果如下所示:
我们也可以用数学方法来解决这个问题,减少循环次数:
设:
- 10 元数量为 x
- 5 元数量为 y
- 2 元数量为 z
- 1 元数量为 w
则有:
- 10x + 5y + 2z + w = 100
- x + y + z + w = 40
- x, y, z, w ≥ 1
由方程 2 可得:w = 40 - x - y - z ,代入方程 1:
10x + 5y + 2z + (40 - x - y - z) = 100
=> 9x + 4y + z = 60
现在我们需要找到满足:
- x ∈ [1,9]
- y ∈ [1, (60-9x-1)/4] (因为 z ≥ 1)
- z = 60 - 9x - 4y 且 z ≥ 1
#include <stdio.h>int main()
{int count = 0; // 初始化计数器,用于记录符合条件的组合数// 遍历 10 元纸币的可能数量(x 的取值范围是 1 到 9,因为至少 1 张,最多 9 张)for (int x = 1; x <= 9; x++){// 计算 5 元纸币的最大可能数量// 根据方程:9x + 4y + z = 60,且 z ≥ 1// 所以y的最大值为:(60 - 9x - 1) / 4for (int y = 1; y <= (60 - 9 * x - 1) / 4; y++){// 计算 2 元纸币的数量 z// 根据方程:z = 60 - 9x - 4yint z = 60 - 9 * x - 4 * y;// 检查 z 是否合法(至少 1 张)if (z >= 1){// 计算 1 元纸币的数量 w// 根据总张数方程:x + y + z + w = 40// 所以 w = 40 - x - y - zint w = 40 - x - y - z;// 检查 w 是否合法(至少 1 张)if (w >= 1){// 如果所有条件都满足,增加计数器并打印当前组合count++;printf("组合 %d: 10元x%d, 5元x%d, 2元x%d, 1元x%d\n",count, x, y, z, w);}}}}// 打印符合条件的组合总数printf("总共有 %d 种符合条件的换法\n", count);return 0;
}