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,最大因数是 3
for (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,最大因数是 3
for (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,跳过数字 5
for (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 到 100
for (int i = 1; i <= 100; i++)
{
// 检查当前数字是否是 7 的倍数,个位数是否为 7,或者十位数是否为 7
if (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 到 z
printf("小写字母 a-z:\n");
for (char c = 'a'; c <= 'z'; c++)
{
printf("%c ", c); // 打印当前字母并加空格
}
printf("\n"); // 换行
// 打印大写字母 Z 到 A
printf("大写字母 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; // 用户指定的正整数 n
double sum = 0.0; // 用来存储序列的和
int sign = 1; // 用来表示当前的符号,初始化为正(1)
// 提示用户输入正整数 n
printf("请输入一个正整数 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); // 34
return 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) / 4
for (int y = 1; y <= (60 - 9 * x - 1) / 4; y++)
{
// 计算 2 元纸币的数量 z
// 根据方程:z = 60 - 9x - 4y
int z = 60 - 9 * x - 4 * y;
// 检查 z 是否合法(至少 1 张)
if (z >= 1)
{
// 计算 1 元纸币的数量 w
// 根据总张数方程:x + y + z + w = 40
// 所以 w = 40 - x - y - z
int 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;
}