例题: 成绩统计、完全数判定
练习: 数字三角形、跳水评分、水仙花数
进阶练习 (选做): 回文数、斐波那契、百钱百鸡
组合练习 (选做): 菜单计算器、成绩等级统计
每题都是"先任务后代码", 写完编译运行验证
现场演示: 输入一批学生成绩 (0~100), 直到 -1 结束, 输出最高分以及得最高分的人数
样例:
输入成绩 (-1 结束): 85 92 85 78 92 -1
最高分: 92
人数: 2
提示: 哨兵值 -1 不参与统计; 出现更高的分数时, 人数要重置吗?
#include <stdio.h>
int main(void) {
int score, max = -1, count = 0;
printf("输入成绩 (-1 结束): ");
scanf("%d", &score);
while (score != -1) {
if (score > max) {
max = score; /* 新最高分 */
count = 1; /* 人数重新计 */
} else if (score == max) {
count++; /* 并列最高分 */
}
scanf("%d", &score);
}
printf("最高分: %d\n", max);
printf("人数: %d\n", count);
return 0;
}
max 初始 -1 (成绩都 ≥ 0); 出现更高分时 max 更新、count 重置为 1——这是并列计数的关键
哨兵 -1 只用来结束, 不进统计
与 5-1 的"找最大值"同型, 多了一个并列计数
现场演示: 输入一个整数, 判断它是不是完全数 (等于除自身外的所有因数之和, 如 6 = 1+2+3, 28 = 1+2+4+7+14)
样例:
输入一个数: 28
28 是完全数
输入一个数: 12
12 不是完全数
提示: 完全数等于除自身外的因数之和——怎么找出这些因数?
#include <stdio.h>
int main(void) {
int n, i, sum = 0;
printf("输入一个数: ");
scanf("%d", &n);
for (i = 1; i < n; i++) {
if (n % i == 0) {
sum += i; /* 累加因数 */
}
}
if (sum == n)
printf("%d 是完全数\n", n);
else
printf("%d 不是完全数\n", n);
return 0;
}
枚举 1 ~ n-1, n % i 0 就是因数——模运算判断整除==的经典用法
累加器 sum 初值 0; 循环结束再和 n 比较
拓展: 输出 10000 以内的完全数 (6、28、496、8128)
独立练习: 输入高度 n, 打印数字三角形——第 i 行打印 i 个数字 i
样例 (n = 4):
1
22
333
4444
提示: 嵌套循环——外层管行, 内层管每行个数; 内层打印的是行号还是别的?
#include <stdio.h>
int main(void) {
int n;
printf("输入高度: ");
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
printf("%d", i); /* 打印行号 i */
}
printf("\n");
}
return 0;
}
内层 j 控制个数 (1 到 i), 打印的内容是外层变量 i
常见错误: 内层打印 j (会打成 123/123/123) 或 n (全行一样)
和星号塔同结构, 换了个"打印什么"
独立练习: 跳水比赛 8 个裁判打分 (0~10, 一位小数), 去掉一个最高分、一个最低分, 输出剩余 6 个的平均分 (保留两位)
样例:
输入 8 个分数: 9.5 9.0 8.5 9.5 8.0 9.0 9.5 8.5
最终得分: 9.00
提示: 去掉最高和最低——你需要在循环里同时维护哪几个量?
#include <stdio.h>
int main(void) {
double score, sum = 0, max = -1, min = 11;
int i;
for (i = 0; i < 8; i++) {
scanf("%lf", &score);
sum += score;
if (score > max) max = score;
if (score < min) min = score;
}
printf("最终得分: %.2f\n", (sum - max - min) / 6);
return 0;
}
max 初值 -1、min 初值 11——比所有可能分数都"更差"的起点
循环里同时维护 sum、max、min 三个量
一次遍历搞定, 不需要数组——流式处理的典型
独立练习: 输出所有的三位水仙花数 (各位数字的立方和等于本身, 如 153 = 1³+5³+3³)
样例:
153 370 371 407
提示: 怎么判断一个数是不是水仙花数? 每位数字怎么拆出来? (第 3 周的老朋友)
#include <stdio.h>
int main(void) {
int n, ge, shi, bai;
for (n = 100; n <= 999; n++) {
bai = n / 100;
shi = n / 10 % 10;
ge = n % 10;
if (bai * bai * bai + shi * shi * shi + ge * ge * ge == n)
printf("%d ", n);
}
printf("\n");
return 0;
}
外层枚举所有三位数, 内层每个数拆分 + 判定——枚举与拆分的组合
数位拆分是第 3 周的"先除后模"套路, 这里在循环里批量使用
立方直接连乘 (bai*bai*bai), 不需要 pow
独立练习: 输入一个正整数, 判断它是不是回文数 (正着读和反着读一样)
样例:
输入一个数: 12321
12321 是回文数
输入一个数: 12345
12345 不是回文数
提示: 怎么把数"反过来读"? 数位拆分的老朋友, 但这次要把拆出来的数字重新拼起来
#include <stdio.h>
int main(void) {
int n, m, rev = 0;
printf("输入一个数: ");
scanf("%d", &n);
m = n;
while (m > 0) {
rev = rev * 10 + m % 10; /* 末位接到 rev 后面 */
m /= 10;
}
if (rev == n)
printf("%d 是回文数\n", n);
else
printf("%d 不是回文数\n", n);
return 0;
}
rev = rev * 10 + m % 10 是反转构造的经典一行: 每次把末位"接"到 rev 后面
m 复制了 n——n 要留到最后比较, 不能改
数位拆分 (水仙花) 是"拆", 这里是"拆了再拼"——同一套 % 10 / / 10 的两种用法
独立练习: 输入 n, 输出斐波那契数列的前 n 项 (1、1、2、3、5、8、…)
样例 (n = 10):
1 1 2 3 5 8 13 21 34 55
提示: 每项是前两项之和——需要同时记住哪两个数? 每轮它们怎么变?
#include <stdio.h>
int main(void) {
int n, i, a = 1, b = 1, t;
printf("输入 n: ");
scanf("%d", &n);
for (i = 1; i <= n; i++) {
printf("%d ", a);
t = a + b; /* 下一项 */
a = b; /* 滚动: 旧 b 变新 a */
b = t; /* 新 b 是刚才的和 */
}
printf("\n");
return 0;
}
两个变量 a、b 滚动更新——循环里"记住前两项"的标准模型
每轮: 打印 a → 算下一项 t → a、b 各往前挪一格
这个数列第 7 周递归会再见——到时对比两种写法
独立练习: 公鸡 5 文一只、母鸡 3 文一只、小鸡 1 文 3 只。用 100 文钱买 100 只鸡, 输出所有方案
样例:
公鸡 0 母鸡 25 小鸡 75
公鸡 4 母鸡 18 小鸡 78
公鸡 8 母鸡 11 小鸡 81
公鸡 12 母鸡 4 小鸡 84
提示: 公鸡和母鸡的只数都枚举一遍, 小鸡只数能推出来吗? 价钱条件怎么写?
#include <stdio.h>
int main(void) {
int g, h, c;
for (g = 0; g <= 20; g++) { /* 公鸡最多 20 只 */
for (h = 0; h <= 33; h++) { /* 母鸡最多 33 只 */
c = 100 - g - h; /* 小鸡只数: 推出来 */
if (5 * g + 3 * h + c / 3 == 100 && c % 3 == 0)
printf("公鸡 %d 母鸡 %d 小鸡 %d\n", g, h, c);
}
}
return 0;
}
枚举两个、推导第三个: c 由 g、h 直接算出, 少一层循环
条件两个: 总价 5g + 3h + c/3 100, 且小鸡数 c % 3 0 (不能买半只)
共 4 组解——第一次体会"循环暴力求解"的快感
独立练习: 写一个菜单计算器——循环显示菜单, 用户选择运算, 直到选择退出
样例:
1. 加法
2. 减法
3. 退出
请选择: 1
输入两个数: 3 5
3 + 5 = 8
请选择: 2
输入两个数: 10 4
10 - 4 = 6
请选择: 3
再见!
提示: 菜单要反复出现——哪种循环最合适? 选择项用什么结构处理? 退出要跳出哪一层?
#include <stdio.h>
int main(void) {
int choice, a, b;
do {
printf("1. 加法\n2. 减法\n3. 退出\n");
printf("请选择: ");
scanf("%d", &choice);
if (choice == 1 || choice == 2) {
printf("输入两个数: ");
scanf("%d%d", &a, &b);
}
switch (choice) {
case 1: printf("%d + %d = %d\n", a, b, a + b); break;
case 2: printf("%d - %d = %d\n", a, b, a - b); break;
case 3: printf("再见!\n"); break;
default: printf("无效选择\n");
}
} while (choice != 3);
return 0;
}
do-while + switch 的经典组合: 菜单至少出现一次, 选项用 switch 分发
choice != 3 控制循环结束——switch 里的 break 只跳出 switch, 不会跳出循环
非法输入走 default, 循环继续——菜单程序天然容错
独立练习: 输入一批成绩 (0~100, -1 结束), 统计各等级人数: 90~100 为 A, 80~89 为 B, 70~79 为 C, 60~69 为 D, 0~59 为 F
样例:
输入成绩 (-1 结束): 85 92 78 61 45 92 -1
A: 2 B: 1 C: 1 D: 1 F: 1
提示: 怎么把 0~100 的成绩划分成 A~F 五档? 每个等级需要一个什么?
#include <stdio.h>
int main(void) {
int score, a = 0, b = 0, c = 0, d = 0, f = 0;
printf("输入成绩 (-1 结束): ");
scanf("%d", &score);
while (score != -1) {
switch (score / 10) {
case 10: case 9: a++; break;
case 8: b++; break;
case 7: c++; break;
case 6: d++; break;
default: f++; break;
}
scanf("%d", &score);
}
printf("A: %d B: %d C: %d D: %d F: %d\n", a, b, c, d, f);
return 0;
}
score / 10 把 0~100 映射成 10 个档——switch 的 case 就是档位
case 10: case 9: 共用一个语句 (A 档)——第 4 周学的多 case 标签在这里兑现
忘写 break 会串档 (所有档都往下漏)——统计类程序的经典 bug