摘要 :猜数字游戏把分支、循环和输入输出串在一起:电脑随机出一个 1~100 的数,玩家反复猜,程序提示大了或小了。本文先讲
rand、srand、time怎样配合产生随机数,再搭出「菜单 + 一局游戏」的do-while与switch骨架,最后处理猜对后的多层跳出,以及scanf读到非数字时为什么会卡死。
文章目录
- [1. 需求与最终效果](#1. 需求与最终效果)
- [2. 随机数:rand、srand、time](#2. 随机数:rand、srand、time)
-
- [2.1 rand 的默认序列](#2.1 rand 的默认序列)
- [2.2 srand 与 time](#2.2 srand 与 time)
- [2.3 映射到 1~100](#2.3 映射到 1~100)
- [3. 主循环与一局游戏](#3. 主循环与一局游戏)
-
- [3.1 菜单与一局游戏](#3.1 菜单与一局游戏)
- [3.2 猜对后为什么回到菜单](#3.2 猜对后为什么回到菜单)
- [4. 测试与常见错误](#4. 测试与常见错误)
-
- [4.1 输入不是数字:scanf 的返回值与缓冲区](#4.1 输入不是数字:scanf 的返回值与缓冲区)
- [4.2 漏写 break 的贯穿](#4.2 漏写 break 的贯穿)
- [4.3 常见错误表](#4.3 常见错误表)
- [5. 扩展:次数与计时](#5. 扩展:次数与计时)
- [6. 小结](#6. 小结)
1. 需求与最终效果
游戏的要求是:显示菜单,输入 1 开始、0 退出;电脑随机生成 1~100 的数;玩家反复猜,程序提示大了或小了;猜中后可以再玩一局。
先运行一次:菜单选 1,猜 50 得到「猜大了」,猜 25 得到「猜小了」,直到猜中答案,程序回到菜单。选 0 时打印「游戏结束」,程序退出。
菜单的三个分支对应三种结果:
| 输入 | 程序行为 |
|---|---|
| 1 | 生成答案,进入猜数字 |
| 0 | 打印「游戏结束」,退出 |
| 其他整数 | 提示选择错误,重新显示菜单 |
2. 随机数:rand、srand、time
2.1 rand 的默认序列
rand 返回 0 到 RAND_MAX 之间的伪随机整数,需要包含 <stdlib.h>。RAND_MAX 至少为 32767,VS2026 里就是 32767。
c
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
for (int i = 0; i < 5; i++)
printf("%d\n", rand());
return 0;
}
输出:
text
41
18467
6334
26500
19169
把程序运行两次,两次输出完全相同。rand 生成的是伪随机序列,序列由种子决定;不调用 srand 时,默认种子是 1,所以每次运行都从同一串数开始。
2.2 srand 与 time
srand 用来设置种子。time(NULL) 返回当前时间戳,即 1970 年 1 月 1 日 0 时 0 分 0 秒到现在的秒数。把它转成 unsigned int 传给 srand,每次运行的种子就不同:
c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void)
{
srand((unsigned int)time(NULL));
for (int i = 0; i < 5; i++)
printf("%d\n", rand());
return 0;
}
这次连续运行两次,前几个数不一样。time_t 可能是 64 位,而 srand 的参数是 unsigned int,所以要强制转换;截断不影响「每次运行种子不同」这个目的。
srand 一次程序只调用一次。若放进每局游戏里,同一秒内开两局会拿到同一个种子,两局的答案就相同。
2.3 映射到 1~100
把 rand() 的返回值映射到指定范围,用取模:
| 目标范围 | 写法 |
|---|---|
| 0~99 | rand() % 100 |
| 1~100 | rand() % 100 + 1 |
| a~b | a + rand() % (b - a + 1) |
范围不能整除 RAND_MAX + 1 时,取模会有极轻微的偏差;小游戏可以忽略,做统计模拟则不能直接这么用。

3. 主循环与一局游戏
3.1 菜单与一局游戏
菜单要反复出现,用 do-while:先显示一次,玩完一局再判断是否继续。switch 根据输入分到三个分支。
c
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void)
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
printf("***********************\n");
printf("****** 1. play ******\n");
printf("****** 0. exit ******\n");
printf("***********************\n");
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
{
int r = rand() % 100 + 1;
int guess = 0;
while (1)
{
printf("请猜数字>:");
scanf("%d", &guess);
if (guess < r)
printf("猜小了\n");
else if (guess > r)
printf("猜大了\n");
else
{
printf("恭喜你,猜对了\n");
break;
}
}
break;
}
case 0:
printf("游戏结束\n");
break;
default:
printf("选择错误,重新选择\n");
break;
}
} while (input);
return 0;
}
case 1 后面用一对大括号,把这一局的变量 r、guess 圈在一个块里。这样变量的作用域只在这一局,不会和其他分支冲突。r 是答案,guess 是玩家的输入。
while (1) 的循环体负责一局:读入猜测、比较、猜中后 break。srand 放在 main 开头,只调用一次,每局由 rand 接着取序列中的下一个数。

把 srand 的参数临时改成 1,答案就固定为 42。依次输入 1 50 25 37 43 42 0,实际输出:
text
***********************
****** 1. play ******
****** 0. exit ******
***********************
请选择:>请猜数字>:猜大了
请猜数字>:猜小了
请猜数字>:猜小了
请猜数字>:猜大了
请猜数字>:恭喜你,猜对了
***********************
****** 1. play ******
****** 0. exit ******
***********************
请选择:>游戏结束
提示串末尾没有换行,输入的回显会紧贴在提示后面,所以 请选择:> 和 请猜数字>: 挤在同一行。这是正常现象。
3.2 猜对后为什么回到菜单
猜中后,while (1) 里的 break 只跳出一层;接着执行 case 1 末尾的 break,跳出 switch;do-while 再判断 while (input)。
input 还是 1,条件为真,于是回到菜单。两处 break 和循环条件各管一件事:
| 位置 | 作用 |
|---|---|
while (1) 里的 break |
结束一局猜数字 |
case 1 末尾的 break |
结束 switch |
while (input) |
决定是否再显示菜单,不受 break 控制 |
如果想让猜中后直接结束程序,可以在 while (1) 结束后把 input 设为 0。按本游戏的要求,猜中后应该回到菜单,所以保留 input 的值。
4. 测试与常见错误
4.1 输入不是数字:scanf 的返回值与缓冲区
scanf 的返回值是成功读入的项数。输入 abc 时返回 0,guess 保持原值,坏字符还留在输入缓冲区里。
下一轮 scanf 又读到同一个 a,再次失败。while (1) 条件恒真,guess 一直是 0,答案又比它大,屏幕上会不断打印「猜小了」。
程序不会自己停下来。修复要同时做两件事:检查返回值,丢弃本行剩余字符。
c
#include <stdio.h>
int main(void)
{
int guess = 0;
while (1)
{
printf("请输入整数>:");
if (scanf("%d", &guess) != 1)
{
int c;
while ((c = getchar()) != '\n' && c != EOF)
;
printf("输入无效,重新输入\n");
continue;
}
printf("收到:%d\n", guess);
if (guess == 0)
break;
}
return 0;
}
依次输入 abc、7、x、9、0,输出:
text
请输入整数>:输入无效,重新输入
请输入整数>:收到:7
请输入整数>:输入无效,重新输入
请输入整数>:收到:9
请输入整数>:收到:0
getchar 每次读一个字符,返回 int;一直读到行尾 '\n' 或文件结束 EOF,缓冲区里的 a、b、c 就被清掉了。continue 回到 while (1) 开头,重新打印提示。
菜单那一层也有同样的问题:首次输入 abc,input 保持初值 0,switch 走 case 0,程序直接退出;如果之前 input 是 1,就会重复上一局的选择。每一处读整数的地方,都要照着检查返回值和清缓冲区。
4.2 漏写 break 的贯穿
漏写 case 1 末尾的 break,执行会贯穿到 case 0:
c
case 1:
{
/* 猜数字循环,猜中后 break */
}
/* 此处漏写 break */
case 0:
printf("游戏结束\n");
break;
屏幕上会打印「游戏结束」,但 input 仍是 1,while (input) 依然为真,程序回到菜单,没有退出。
case 只是入口标签,执行到下一个 case 前不会自动停下。每个分支末尾要不要写 break,要逐个确认。
4.3 常见错误表
| 症状 | 原因 | 改法 |
|---|---|---|
| 同一秒开两局,答案相同 | srand 写在每局里 |
只在 main 开头调用一次 |
| 猜对后打印「游戏结束」却没退出 | case 1 漏 break,贯穿到 case 0 |
补 break;退出只看 while (input) |
| 输入字母后不断打印「猜小了」 | scanf 失败,坏字符留在缓冲区 |
检查返回值,清到行尾 |
| 首次菜单输入字母就退出 | input 保持初值 0 |
菜单输入也做同一套检查 |
| 答案总是同一个 | 没调 srand,或种子固定 |
用 time(NULL) 设种子,只调一次 |
| 随机数范围偏大或偏小 | 取模公式写错 | a + rand() % (b - a + 1) |
5. 扩展:次数与计时
限制只能猜 5 次,先在文件开头加 #define MAX_TRY 5,再把 while (1) 换成 for。下面只列 case 1 的内容,其余与上一节相同:
c
case 1:
{
int r = rand() % 100 + 1;
int guess = 0;
int i = 0;
time_t begin = time(NULL);
for (i = 1; i <= MAX_TRY; i++)
{
printf("第 %d 次机会,请猜数字>:", i);
scanf("%d", &guess);
printf("已用时 %.0f 秒\n", difftime(time(NULL), begin));
if (guess < r)
printf("猜小了\n");
else if (guess > r)
printf("猜大了\n");
else
{
printf("恭喜你,猜对了\n");
break;
}
}
if (i == MAX_TRY + 1)
printf("游戏失败,正确答案是 %d\n", r);
break;
}
for 正常走完时,i 会执行最后一次 i++,变成 MAX_TRY + 1;中途猜对用 break 退出,i 不会超过 MAX_TRY。用这个差值判断是否失败。
计时用 time(NULL) 记录开始时刻,每轮再用 difftime 求差值。difftime 返回 double,单位是秒;time 的精度是秒,所以差值也只能精确到秒,快速输入时每轮都显示 0 秒。
把种子固定成 1,依次输入 1 50 25 37 43 44 1 68 0。第一局 5 次都没中,第二局一次猜中,输出节选:
text
第 1 次机会,请猜数字>:已用时 0 秒
猜大了
第 2 次机会,请猜数字>:已用时 0 秒
猜小了
第 3 次机会,请猜数字>:已用时 0 秒
猜小了
第 4 次机会,请猜数字>:已用时 0 秒
猜大了
第 5 次机会,请猜数字>:已用时 0 秒
猜大了
游戏失败,正确答案是 42
第 1 次机会,请猜数字>:已用时 0 秒
恭喜你,猜对了
第一局答案是 42,第二局是 68。srand 只在 main 里调一次,每开一局就接着取序列里的下一个数,所以每局的答案是新的。
6. 小结
rand生成 0~RAND_MAX的伪随机数,默认种子是 1,所以每次运行结果相同。srand((unsigned int)time(NULL))用时间戳设置种子,一次程序调用一次;范围公式是a + rand() % (b - a + 1)。- 菜单用
do-while保证至少显示一次,switch负责分发;程序是否退出只看while (input)。 - 一局游戏用
while (1)加break;break只跳出最近一层,跳完while还要跳switch。 - 漏写
case末尾的break会贯穿,屏幕上打印了退出提示,程序却可能没有退出。 scanf失败时要检查返回值并清空本行,否则输入缓冲区里的坏字符会让循环卡死。- 限制次数用
for,正常结束时i == MAX_TRY + 1;difftime返回秒差,精度到秒。