【C语言】猜数字游戏

摘要 :猜数字游戏把分支、循环和输入输出串在一起:电脑随机出一个 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 返回秒差,精度到秒。
相关推荐
LabVIEW开发2 小时前
CTM-3 红外测温仪 + LabVIEW 温度怎么测?
算法·labview·测试·labview程序
纪念 2292 小时前
C++算法(六)
开发语言·c++·算法
释厄6232 小时前
AI 军备竞赛悖论——为 AI 卖游戏但 AI 需要游戏母场景
人工智能·游戏·kotlin
Dumbness3 小时前
UIA 读微信群消息没有 id,怎么判断哪条是新的:后缀对齐在翻历史和撤回时整屏重报
python·算法
taka3 小时前
从 24x 到 141x:我用纯 Python 零依赖项目,扒开了 GPU 加速的性能黑洞
算法
UIU1143 小时前
P16237 [蓝桥杯 2026 省 B] 应急布线
数据结构·c++·算法·蓝桥杯
旖旎夜光4 小时前
LeetCode 315: 算右侧小于当前元素的个数(归并排序) —— 题解
c++·学习·算法·leetcode·排序算法
像风一样自由20204 小时前
2.如何用FastAPI把一个Python算法封装成HTTP服务
python·算法·fastapi
D e re k ZH4 小时前
保序回归:从 PAVA 到可并堆的单调约束优化
c++·算法·数据挖掘·回归·kotlin