Appearance
循环:让程序重复做事
预计学习时长:约 3 小时(仅 L1 主线,建议分两次完成)· 运行环境:VS2026 / gcc / clang 均可
🎯 学习目标
学完本讲,你将能够:
- 说出 while、for、do-while 三种循环的语法和执行顺序
- 根据问题特点选择合适的循环
- 用 break 提前结束循环,用 continue 跳过本次循环
- 独立写出打印 1~10、统计位数、判断素数这类循环程序
- 完成一个有菜单、能反复玩的猜数字游戏
🔗 先修知识
- 第 3 讲:数据类型和变量:本讲大量使用 int 变量
- 第 4 讲:运算符详解:要用到取余
%、自增++、复合赋值+= - 第 5 讲:分支:猜数字游戏要用 if 和 switch
先修自检(三问)
- C 语言里用什么表示“真”,什么表示“假”?
i++执行后 i 会怎样变化?1234 % 10和1234 / 10的结果分别是多少?
答不上来也没关系,先点开上面对应链接快速复习,再回来继续。
一、为什么需要循环
场景:在屏幕上打印 100 遍“好好学习”。不用循环要写 100 行 printf;用循环,三行就够。
循环就是让计算机重复执行同一段代码,直到条件不再成立。C 语言提供三种循环:
| 循环 | 一句话特点 |
|---|---|
| while | 先判断,再执行 |
| for | 把“初始化、判断、调整”三件事写在一行 |
| do-while | 先执行一次,再判断 |
下面逐个学习。
二、while 循环
2.1 语法
🧩 示意片段
c
while (表达式)
语句; // 循环体;想放多条语句,用 { } 包起来执行规则只有一句话:表达式非 0 就执行循环体,执行完回来再判断;表达式为 0,循环结束。 🟢
2.2 执行流程
text
开始
│
▼
表达式非 0? ─── 否 ──► 循环结束
│
是
▼
执行循环体
│
└──────────── 回到判断2.3 和 if 的区别
if (表达式) 条件成立只执行一次;while (表达式) 条件成立会反复执行。所以 while (1) 中条件永远非 0,循环体永远重复——这叫死循环。
🧩 示意片段(不要直接运行,它会无限打印;控制台窗口按 Ctrl+C 可强行停止,VS 里按 Shift+F5 停止调试)
c
while (1)
printf("hehe\n");2.4 完整例子:打印 1~10
📄 print_ten.c · ✅ 完整程序(可直接复制编译)
c
#include <stdio.h>
int main()
{
int i = 1; // ① 初始化:从 1 开始
while (i <= 10) // ② 判断:i 不超过 10
{
printf("%d ", i);
i = i + 1; // ③ 调整:每次加 1,让循环能走到终点
}
printf("\n");
return 0;
}运行结果:
text
1 2 3 4 5 6 7 8 9 10写循环先想清楚三件事:从几开始(初始化)、什么时候停(判断)、每次怎么变(调整)。少了“调整”,i 永远是 1,就变成死循环。
2.5 例子:逆序打印整数的每一位
输入 1234,输出 4 3 2 1。先看思路:
1234 % 10得到最低位 41234 / 10去掉最低位,得到 123- 重复上面两步,直到数字变成 0
这个程序要用到 scanf。第一次用 scanf,先解决一个 VS 下必遇的报错。
⚠️ VS 用户必看:error C4996: 'scanf'
在 VS2026 里编译用到 scanf 的程序,会出现红色报错 C4996,提示 scanf 不安全。这是微软自己加的警告(gcc、clang 都不会报),三种解决办法任选一种:
- 文件最顶部(所有 #include 之前)加一行
#define _CRT_SECURE_NO_WARNINGS,本课所有完整程序都已经加好 - 在文件顶部加
#pragma warning(disable:4996) - 一劳永逸:用记事本以管理员身份打开 VS 安装目录下的
newc++file.cpp(VS 新建 .c 文件的模板),把第 1 条的宏写进去保存,以后新建文件自动带上
推荐用办法 1 或 3。
📄 reverse_digits.c · ✅ 完整程序
c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int n = 0;
scanf("%d", &n);
while (n != 0)
{
printf("%d ", n % 10); // 打印最低位
n = n / 10; // 去掉最低位
}
printf("\n");
return 0;
}运行结果(输入 1234 后回车):
text
1234
4 3 2 1📘 提高(L2):为什么写 while (n) 也行
n != 0 的判断结果是 0 或 1,而 n 本身在非 0 时就代表真、为 0 时代表假,所以 while (n) 和 while (n != 0) 完全等价。类似地,if (flag) 和 if (flag != 0) 等价。新手阶段建议写完整形式,可读性更好。
三、for 循环
3.1 语法
while 把初始化、判断、调整分散在三个地方,循环一长就容易看花眼。for 把它们集中在一行:
🧩 示意片段
c
for (表达式1; 表达式2; 表达式3)
语句;| 部分 | 作用 | 执行时机 |
|---|---|---|
| 表达式 1 | 初始化循环变量 | 整个循环开始时,只执行 1 次 |
| 表达式 2 | 判断条件 | 每次进入循环体之前判断 |
| 表达式 3 | 调整循环变量 | 每次循环体执行之后执行 |
3.2 执行顺序
按序号走一遍:① 表达式 1(只一次)→ ② 判断表达式 2,为 0 就结束 → ③ 执行循环体 → ④ 执行表达式 3 → 回到 ②。
3.3 完整例子:打印 1~10
📄 for_ten.c · ✅ 完整程序
c
#include <stdio.h>
int main()
{
for (int i = 1; i <= 10; i++)
{
printf("%d ", i);
}
printf("\n");
return 0;
}for (int i = 1; ...) 中的 i 只在这个循环里有效(C99 起允许在表达式 1 中声明变量),VS 和 gcc 都支持。
3.4 while 和 for 怎么选
两者能完成完全相同的事。经验法则:循环次数明确、靠计数控制的,优先用 for;只知道停止条件、不知道要循环多少次的,用 while。
📘 提高(L2):for 的三个表达式都可以省略
C 语法允许省略表达式:省略表达式 2 表示条件永远非 0,for(;;) 就是死循环。但不推荐新手这么写,可读性差。
3.5 例子:1~100 中 3 的倍数之和
📄 sum_multiples.c · ✅ 完整程序
c
#include <stdio.h>
int main()
{
int sum = 0;
for (int i = 1; i <= 100; i++)
{
if (i % 3 == 0)
sum = sum + i;
}
printf("%d\n", sum);
return 0;
}运行结果:
text
1683还可以更省事——让 i 直接从 3 开始、每次加 3,连 if 判断都不需要:
📄 sum_multiples2.c · ✅ 完整程序
c
#include <stdio.h>
int main()
{
int sum = 0;
for (int i = 3; i <= 100; i += 3)
{
sum += i;
}
printf("%d\n", sum);
return 0;
}四、do-while 循环
4.1 语法
🧩 示意片段
c
do
{
语句;
} while (表达式); // ⚠️ 末尾的分号不能漏!4.2 执行顺序
和前两种最大的不同:先执行一次循环体,再判断表达式。所以无论条件如何,循环体至少执行一次。这也是名字的由来:先 do(做),再 while(判断要不要继续)。
4.3 完整例子:打印 1~10
📄 do_ten.c · ✅ 完整程序
c
#include <stdio.h>
int main()
{
int i = 1;
do
{
printf("%d ", i);
i++;
} while (i <= 10);
printf("\n");
return 0;
}如果漏写末尾分号,gcc 会报 expected ';' 一类的错误,记得检查。
4.4 例子:统计一个整数是几位数
输入 1234 输出 4;输入 0 输出 1——注意,0 也是一位数,循环体必须先执行一次,这种场景正好适合 do-while:
📄 count_digits.c · ✅ 完整程序
c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int n = 0;
int cnt = 0;
scanf("%d", &n);
do
{
cnt++; // 位数加 1
n = n / 10; // 去掉最低位
} while (n != 0);
printf("%d\n", cnt);
return 0;
}4.5 三种循环怎么选
| 场景 | 推荐 |
|---|---|
| 次数明确,如打印 100 次、遍历编号 | for |
| 只知道停止条件,如读到 0 为止 | while |
| 循环体必须先执行一次,如统计位数、菜单选择 | do-while |
五、break 和 continue
5.1 break:立即结束整个循环
break 执行后,直接跳出当前这一层循环,循环后面的代码继续执行:
📄 break_demo.c · ✅ 完整程序
c
#include <stdio.h>
int main()
{
for (int i = 1; i <= 10; i++)
{
if (i == 5)
break; // i 等于 5 时,整个循环结束
printf("%d ", i);
}
printf("\n");
return 0;
}运行结果:
text
1 2 3 45.2 continue:跳过本次,进入下一次
continue 只跳过本次循环中它后面的代码,循环本身不结束:
📄 continue_demo.c · ✅ 完整程序
c
#include <stdio.h>
int main()
{
for (int i = 1; i <= 10; i++)
{
if (i == 5)
continue; // 只跳过 i=5 这一次
printf("%d ", i);
}
printf("\n");
return 0;
}运行结果:
text
1 2 3 4 6 7 8 9 105.3 高频坑:while 里的 continue 可能造成死循环
🧩 示意片段(这段代码会死循环,先别运行)
c
int i = 1;
while (i <= 10)
{
if (i == 5)
continue; // 直接跳回 while 判断处!
printf("%d ", i);
i++; // ⚠️ i=5 时这行永远执行不到,i 一直是 5
}原因:在 while 中 continue 直接跳回条件判断,而 i++ 在 continue 后面,被永久跳过。for 没有这个问题——它的调整(表达式 3)在循环体外面,continue 之后仍会执行。
修法:改用 for,或确保调整不被 continue 跳过:
📄 continue_fix.c · ✅ 完整程序
c
#include <stdio.h>
int main()
{
for (int i = 1; i <= 10; i++)
{
if (i == 5)
continue;
printf("%d ", i);
}
printf("\n");
return 0;
}5.4 一张表分清二者
| break | continue | |
|---|---|---|
| 作用 | 结束整个循环 | 只跳过本次 |
| 之后去哪 | 循环之后的代码 | 下一次循环的判断/调整 |
六、循环的嵌套
循环里面还可以再放循环,叫嵌套循环。外层循环每走 1 次,内层循环要完整走完一整轮。
经典例子:找出 100~200 之间的素数。素数(也叫质数)是只能被 1 和它本身整除的数,比如 101、103。
判断思路:对每个数 i,用 2 到 i-1 逐个试除;只要有一个能整除,就不是素数;全都不能整除,才是素数。用一个标记变量 flag 记录结论:
📄 primes.c · ✅ 完整程序
c
#include <stdio.h>
int main()
{
for (int i = 100; i <= 200; i++)
{
int flag = 1; // 1 表示“先假设是素数”
for (int j = 2; j < i; j++)
{
if (i % j == 0)
{
flag = 0; // 找到了因数,不是素数
break; // 内层循环没必要再试
}
}
if (flag == 1)
printf("%d ", i);
}
printf("\n");
return 0;
}运行结果:
text
101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199📘 提高(L2):让试除次数少一半
判断 i 是不是素数,其实不用试到 i-1:如果 i 有一个大于 √i 的因数,就一定对应一个小于 √i 的因数,所以内层条件改成 j * j <= i 就足够。另外除了 2,其他素数都是奇数,外层可以直接跳过偶数。优化后的完整写法放在本讲末尾的 L3 拓展。
七、goto 语句
goto 可以在同一个函数内跳到任意标号处:
📄 goto_demo.c · ✅ 完整程序
c
#include <stdio.h>
int main()
{
printf("hehe\n");
goto next; // 跳到标号 next
printf("haha\n"); // 这行被跳过
next: // 标号:名字加冒号
printf("跳过了 haha 的打印\n");
return 0;
}随意使用 goto 会让程序跳来跳去、难以阅读(这种代码叫“面条代码”),所以平时不要用 goto。但它有一个公认合适的场景:一次跳出多层循环——break 一次只能跳一层,而 goto 一步到位:
🧩 示意片段
c
for (...)
{
for (...)
{
if (出现严重错误)
goto error; // 一次跳出两层
}
}
error:
// 统一处理错误📘 提高(L2):goto 真的一无是处吗
Linux 内核这类大型 C 工程大量使用 goto,而且用法非常统一:只向下跳、只跳到函数末尾做资源清理(释放内存、关闭文件)。在这种约定下,goto 反而让错误处理更清晰。所以准确的说法是“不要随意用 goto”,而不是“绝对不能用”。
八、综合实战:猜数字游戏
8.1 游戏规则
- 程序随机生成一个 1~100 的整数
- 玩家输入猜测,程序提示“猜大了”或“猜小了”
- 猜对后结束,并有菜单可以再玩一局
先解决核心问题:随机数从哪来。
8.2 rand:生成随机数
🧩 函数原型(来自 stdlib.h)
c
int rand(void); // 返回 0 ~ RAND_MAX 之间的整数,RAND_MAX 至少是 32767直接调用 rand 有个特点:每次运行程序,产生的数字顺序完全一样。因为它是“伪随机数”——靠固定公式算出来的,需要先给它一个不同的起点,这个起点叫“种子”。
8.3 srand 和 time:每次运行都不同
🧩 函数原型
c
void srand(unsigned int seed); // 设置随机种子
long time(NULL); // time.h,返回当前时间戳把当前时间转换成种子,每次运行种子不同,随机数序列就不同。整个程序只需在开头调用一次 srand。下面程序每次运行打印 5 个随机数:
📄 rand_demo.c · ✅ 完整程序
c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
srand((unsigned int)time(NULL));
for (int i = 0; i < 5; i++)
printf("%d ", rand());
printf("\n");
return 0;
}多运行几次,每次结果都不同。
8.4 把随机数限制到指定范围
🧩 示意片段
c
rand() % 100; // 余数范围 0 ~ 99
rand() % 100 + 1; // 1 ~ 100,猜数字用这个
a + rand() % (b - a + 1); // 通用公式:a ~ b8.5 完整游戏
📄 guess_game.c · ✅ 完整程序
c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void menu()
{
printf("***********************\n");
printf("****** 1. play ******\n");
printf("****** 0. exit ******\n");
printf("***********************\n");
}
void game()
{
int answer = rand() % 100 + 1; // 生成 1~100 的答案
int guess = 0;
while (1)
{
printf("请猜数字: ");
scanf("%d", &guess);
if (guess < answer)
printf("猜小了\n");
else if (guess > answer)
printf("猜大了\n");
else
{
printf("恭喜你,猜对了!\n");
break; // 猜对,结束本轮
}
}
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL)); // 只设置一次种子
do
{
menu();
printf("请选择: ");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("游戏结束\n");
break;
default:
printf("选择错误,重新选择\n");
break;
}
} while (input);
return 0;
}这个程序把本讲和前几讲的知识全用上了:do-while 做菜单循环、switch 处理选择、while(1) + break 控制猜数过程、函数拆分功能。建议亲手敲一遍再继续。
8.6 进阶:限制猜测次数
📘 提高(L2):5 次机会版 game 函数
把 8.5 中的 game() 整个替换成下面版本即可,其余代码不动:
c
void game()
{
int answer = rand() % 100 + 1;
int guess = 0;
int chances = 5; // 最多猜 5 次
while (chances > 0)
{
printf("你还有 %d 次机会\n", chances);
printf("请猜数字: ");
scanf("%d", &guess);
if (guess < answer)
printf("猜小了\n");
else if (guess > answer)
printf("猜大了\n");
else
{
printf("恭喜你,猜对了!\n");
break;
}
chances--;
}
if (chances == 0)
printf("你失败了,正确值是:%d\n", answer);
}⚠️ 常见坑与报错(本讲汇总)
| 你看到的现象 | 原因 | 修法 |
|---|---|---|
| VS 报 error C4996: 'scanf' | VS 认为 scanf 不安全 | 文件顶部加 #define _CRT_SECURE_NO_WARNINGS |
| 程序停不下来、疯狂打印 | 死循环:忘了调整变量,或 continue 跳过了调整 | 检查 i++ 位置;for 中 continue 仍会执行表达式 3 |
| gcc 报 expected ';' | do-while 末尾漏了分号 | } while (...); |
| 循环体一次都没按预期执行 | for/while 后面误写了分号,空语句成了循环体 | 删掉条件括号后面的分号 |
| 下标走到 n 时出异常 | 条件写成 i <= n 多循环一次导致越界 | 用 i < n,详见附录 C |
| 黑框一闪而过 | 没用 Ctrl+F5 启动 | 按 Ctrl+F5,或参考附录 A |
🛠️ 动手练习
规则:先独立做,卡住了再依次展开提示,不要一上来就看答案。
📊 本讲网页练习进度0 / 5(0%)
进度自动保存在本浏览器;编程题不计数,请在编辑器中完成。
练习 1 · ⭐ 基础 · 选择判断
知识点:for 的执行顺序
题干:关于 for (int i = 0; i < 3; i++) printf("%d ", i);,下面哪个说法正确?
练习 2 · ⭐ 基础 · 读输出
知识点:break 跳出的位置
题干:阅读代码,写出输出:
📄 ex2.c · ✅ 完整程序
c
#include <stdio.h>
int main()
{
int i = 1;
while (i <= 10)
{
if (i == 4)
break;
printf("%d ", i);
i++;
}
printf("结束\n");
return 0;
}请在下面写出程序的完整输出(同一行内容之间用一个空格即可):
空①
练习 3 · ⭐ 基础 · 读输出
知识点:步长不为 1 的循环
题干:阅读代码,写出输出:
📄 ex3.c · ✅ 完整程序
c
#include <stdio.h>
int main()
{
for (int i = 10; i >= 1; i -= 3)
printf("%d ", i);
printf("\n");
return 0;
}请在下面写出程序的完整输出:
空①
练习 4 · ⭐ 基础 · 找错改错
知识点:循环条件与越界
题干:下面代码想给数组的 5 个元素赋值,却存在错误,请找出并改正:
🧩 待改错片段
c
int arr[5];
for (int i = 0; i <= 5; i++)
arr[i] = i;正确的修法是哪一个?
练习 5 · ⭐ 基础 · 程序填空
知识点:累加与计数模板
题干:程序读入 5 个整数,计算它们的和。请填上两个空:
🧩 待补全片段
c
int sum = 0;
for (int i = 1; i <= ______; i++)
{
int x = 0;
scanf("%d", &x);
sum = sum + ______;
}
printf("%d\n", sum);请填出两个空:
空①空②
练习 6 · ⭐⭐ 提高 · 写小程序
知识点:循环 + 拆位运算
🛠 动手编程题 · 请在 VS2026(或你的编辑器)中完成
打印所有“水仙花数”。水仙花数是一个三位数,它各位数字的立方和恰好等于它本身,例如 153 = 1³ + 5³ + 3³。
✅ 过关标准:程序依次输出 153、370、371、407(共 4 个)。
网页内无法练习写代码,亲手敲、亲手编译才能真正学会。下方按顺序展开 思路 → 步骤 → 答案。
💡 提示 1(思路)
三位数范围是 100~999,对每个数拆出百位、十位、个位,分别怎么拆?
🧭 提示 2(步骤)
- 百位:
i / 100;十位:i / 10 % 10;个位:i % 10 - 判断三个立方之和是否等于 i
- 成立就打印
🔑 参考答案与解释
c
#include <stdio.h>
int main()
{
for (int i = 100; i <= 999; i++)
{
int a = i / 100;
int b = i / 10 % 10;
int c = i % 10;
if (a*a*a + b*b*b + c*c*c == i)
printf("%d ", i);
}
printf("\n");
return 0;
}为什么这样拆位:整数除法会丢掉小数部分,/100 得到百位;先 /10 再 %10 得到中间的十位。
练习 7 · ⭐⭐ 提高 · 写小程序
知识点:while + 取余(辗转相除法)
🛠 动手编程题 · 请在 VS2026(或你的编辑器)中完成
输入两个正整数,求它们的最大公约数。提示:用大数对小数取余,若余数不为 0,就把“小数”和“余数”作为新的两个数继续,直到余数为 0,此时的除数就是答案。
✅ 过关标准:输入 24 18 输出 6;输入 7 13 输出 1。
网页内无法练习写代码,亲手敲、亲手编译才能真正学会。下方按顺序展开 思路 → 步骤 → 答案。
💡 提示 1(思路)
准备两个变量 a、b,每轮做一件事:求 a 除以 b 的余数,然后让 a 变成 b、b 变成余数。
🧭 提示 2(步骤)
r = a % ba = b; b = r;- 当 b(余数)变成 0 时停止,打印 a
🔑 参考答案与解释
c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 0, b = 0;
scanf("%d %d", &a, &b);
while (b != 0)
{
int r = a % b;
a = b;
b = r;
}
printf("%d\n", a);
return 0;
}为什么最后 a 是答案:每一步 a、b 的公约数集合都不变,当余数变 0 时,b 整除 a,a 就是最大公约数。这个算法叫“欧几里得算法”,已经有两千多年历史。
练习 8 · ⭐⭐⭐ 挑战 · 改造游戏
知识点:循环计数 + 输入检查的综合运用
🛠 动手编程题 · 请在 VS2026(或你的编辑器)中完成
给猜数字游戏增加两个功能:① 每局最多猜 5 次,失败后公布答案;② 如果玩家输入的不是整数(比如字母),提示“输入无效”而不是让程序行为失控。
✅ 过关标准:猜错 5 次自动结束本局;输入字母后程序不崩溃、能继续正常游戏。
网页内无法练习写代码,亲手敲、亲手编译才能真正学会。下方按顺序展开 思路 → 步骤 → 答案。
💡 提示 1(思路)
功能 ① 在 8.6 的折叠里已经有参考。功能 ② 需要利用 scanf 的返回值:成功读到整数返回 1,失败返回 0。
🧭 提示 2(步骤)
- 用 chances 变量记录剩余次数
- 判断
scanf("%d", &guess) != 1时,用while (getchar() != '\n') ;清掉这行无效输入 - 输入无效不扣次数,直接进入下一轮
🔑 参考答案与解释
增强版 game 函数(替换原 game,其余代码不变):
c
void game()
{
int answer = rand() % 100 + 1;
int guess = 0;
int chances = 5;
while (chances > 0)
{
printf("你还有 %d 次机会,请猜数字: ", chances);
if (scanf("%d", &guess) != 1)
{
printf("输入无效,请输入整数\n");
while (getchar() != '\n')
; // 清空这行垃圾输入
continue; // 不扣次数
}
if (guess < answer)
printf("猜小了\n");
else if (guess > answer)
printf("猜大了\n");
else
{
printf("恭喜你,猜对了!\n");
return;
}
chances--;
}
printf("你失败了,正确值是:%d\n", answer);
}为什么必须清空输入:scanf 读取失败时,无效字符仍留在输入缓冲区,不清空下一次 scanf 又会读到同样内容,造成死循环。
📝 小结与自测
核心结论
- 三种循环:while 先判断后执行;for 把初始化、判断、调整集中一行;do-while 先执行一次再判断。
- break 结束整个循环,continue 只跳过本次;while 中要警惕 continue 跳过调整造成死循环。
- 写循环先确定三件事:起点、停止条件、每次的变化。
- VS 用 scanf 要加
#define _CRT_SECURE_NO_WARNINGS。 - 嵌套循环中,外层走一步,内层走一整轮。
自测清单(能做到就打勾)
| 我能独立做到 | ✅ |
|---|---|
| 用 for 打印 1~100 | |
| 拆出一个整数的每一位 | |
| 统计一个整数是几位数 | |
| 说出 break 和 continue 的区别 | |
| 判断 100~200 中的素数 | |
| 让猜数字游戏在自己电脑上跑起来 |
🔬 选学拓展(L3)
以下内容为选学
现在跳过完全不影响下一讲学习,等你学完本阶段再回来看。
L3-1:更稳健的菜单输入
8.5 的菜单有个漏洞:在菜单处输入字母,scanf 失败,input 保持旧值,do-while 会刷屏式空转。解决办法是检查 scanf 返回值并清空无效输入:
📄 robust_menu.c · ✅ 完整程序
c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void menu()
{
printf("1. play / 0. exit\n");
}
void game()
{
int answer = rand() % 100 + 1;
int guess = 0;
while (1)
{
printf("请猜数字: ");
if (scanf("%d", &guess) != 1)
{
printf("输入无效\n");
while (getchar() != '\n')
;
continue;
}
if (guess == answer)
{
printf("猜对了!\n");
break;
}
printf("%s\n", guess < answer ? "猜小了" : "猜大了");
}
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
printf("请选择: ");
while (scanf("%d", &input) != 1)
{
printf("输入无效,请重新选择\n");
while (getchar() != '\n')
;
menu();
}
if (input == 1)
game();
} while (input != 0);
return 0;
}L3-2:素数算法优化
利用本讲 L2 的结论,内层只试除到 √i,外层只检查奇数:
📄 primes_fast.c · ✅ 完整程序
c
#include <stdio.h>
int main()
{
printf("2 "); // 2 单独处理
for (int i = 3; i <= 200; i += 2) // 只看奇数
{
int flag = 1;
for (int j = 3; j * j <= i; j += 2)
{
if (i % j == 0)
{
flag = 0;
break;
}
}
if (flag)
printf("%d ", i);
}
printf("\n");
return 0;
}判断 200 以内的数,内层最多试到 14,比原来试到 198 快很多。
L3-3:rand 的“取模偏置”
rand() % 100 严格说并不完全均匀。RAND_MAX 常见为 32767,而 32768 不能被 100 整除:0~67 这 68 个数会比其他数多出现一次。对猜数字游戏这点偏差可以忽略,但彩票、抽奖、洗牌等场景就不能直接用它。真正在意随机性时,应使用质量更高的随机源(如各平台提供的加密随机数接口)。这个知识点知道即可。
预告:下一讲进入阶段 2,学习数组——如何用一个名字管理一排数据。