C语言分支和循环(下):猜数字游戏实战与随机数生成详解

📑 目录

  • [🎯 1. 引言](#🎯 1. 引言)
  • [🎲 2. 游戏需求分析](#🎲 2. 游戏需求分析)
  • [🔢 3. 随机数生成](#🔢 3. 随机数生成)
    • [3.1 rand 函数](#3.1 rand 函数)
    • [3.2 srand 函数](#3.2 srand 函数)
    • [3.3 time 函数](#3.3 time 函数)
    • [3.4 组合使用:生成真正的随机数](#3.4 组合使用:生成真正的随机数)
    • [3.5 设置随机数的范围](#3.5 设置随机数的范围)
  • [🎮 4. 猜数字游戏实现](#🎮 4. 猜数字游戏实现)
    • [4.1 基础版本](#4.1 基础版本)
    • [4.2 进阶版本:增加次数限制](#4.2 进阶版本:增加次数限制)
  • [📝 5. 代码结构分析](#📝 5. 代码结构分析)
    • [5.1 函数模块化设计](#5.1 函数模块化设计)
    • [5.2 程序执行流程](#5.2 程序执行流程)
  • [💡 6. 面试常见问题与解答](#💡 6. 面试常见问题与解答)
  • [🏆 7. 总结](#🏆 7. 总结)

🎯 1. 引言

在前面的学习中,我们已经掌握了C语言的分支和循环语句。今天,我们将把这些知识综合运用起来,编写一个非常经典且有趣的小项目------猜数字游戏

通过这个项目,你将深入理解:

  • 如何生成随机数
  • 如何结合循环和分支控制游戏逻辑
  • 如何设计一个完整的程序结构

这个游戏虽然简单,但却是面试中经常被问到的逻辑题,也是你从语法学习迈向项目实战的第一步。

🎲 2. 游戏需求分析

在开始写代码之前,我们先明确一下游戏规则:

  1. 电脑自动生成一个1~100之间的随机数
  2. 玩家猜数字 ,在猜的过程中,程序会根据玩家猜测的数据大小给出 "大了""小了" 的反馈
  3. 直到玩家猜对,游戏结束

这个需求看似简单,但实现它的核心在于:如何让电脑生成一个真正的随机数?

🔢 3. 随机数生成

要想完成猜数字游戏,首先得产生随机数。C语言为我们提供了几个相关的函数。

3.1 rand 函数

C语言提供了一个叫 rand 的函数,它可以生成随机数,函数原型如下:

c 复制代码
int rand (void);
  • rand 函数会返回一个伪随机数 ,范围在 0 ~ RAND_MAX 之间
  • RAND_MAX 的大小依赖编译器实现,大部分编译器上是 32767
  • 使用 rand 函数需要包含头文件:stdlib.h

我们来测试一下 rand 函数,多调用几次,产生5个随机数:

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    printf("%d\n", rand());
    printf("%d\n", rand());
    printf("%d\n", rand());
    printf("%d\n", rand());
    printf("%d\n", rand());
    return 0;
}

运行结果分析:

我们先运行一次,看看结果,再运行一次再看看结果,多运行几次会发现:

  • 第一次运行结果:41 18467 6334 26500 19169
  • 第二次运行结果:41 18467 6334 26500 19169

虽然一次运行中产生的5个数字是相对随机的,但是下一次运行程序生成的结果和上一次一模一样,这就说明有点问题。

💡 知识点: 其实 rand 函数生成的随机数是伪随机数 。伪随机数不是真正的随机数,是通过某种算法生成的随机数。真正的随机数是无法预测下一个值是多少的。而 rand 函数是对一个叫 "种子" 的基准值进行运算生成的随机数。

之所以前面每次运行程序产生的随机数序列是一样的,那是因为 rand 函数生成随机数的默认种子是1。如果要生成不同的随机数,就要让种子是变化的。

3.2 srand 函数

C语言中又提供了一个函数叫 srand,用来初始化随机数的生成器,srand 的原型如下:

c 复制代码
void srand (unsigned int seed);

程序中在调用 rand 函数之前先调用 srand 函数,通过 srand 函数的参数 seed 来设置 rand 函数生成随机数时的种子,只要种子在变化,每次生成的随机数序列也就变化起来了

那也就是说给 srand 的种子如果是随机的,rand 就能生成随机数;但在生成随机数的时候又需要一个随机数,这就矛盾了。

3.3 time 函数

在程序中我们一般是使用程序运行的时间作为种子的,因为时间时刻在发生变化。

C语言中有一个函数叫 time,就可以获得这个时间,time 函数原型如下:

c 复制代码
time_t time (time_t* timer);
  • time 函数会返回当前的日历时间 ,其实返回的是 1970年1月1日0时0分0秒 到现在程序运行时间之间的差值,单位是秒
  • 返回的类型是 time_t 类型的,time_t 类型本质上其实就是32位或者64位的整型类型
  • time 函数的参数 timer 如果是非NULL的指针的话,函数也会将这个返回的差值放在 timer 指向的内存中带回去
  • 如果 timerNULL ,就只返回这个时间的差值。time 函数返回的这个时间差也被叫做:时间戳

使用 time 函数的时候需要包含头文件:time.h

c 复制代码
// VS2022 上 time_t 类型的说明
#ifndef _CRT_NO_TIME_T
    #ifdef _USE_32BIT_TIME_T
        typedef __time32_t time_t;
    #else
        typedef __time64_t time_t;
    #endif
#endif

typedef long                          __time32_t;
typedef __int64                       __time64_t;

如果只是让 time 函数返回时间戳,我们就可以这样写:

c 复制代码
time(NULL); // 调用time函数返回时间戳,这里没有接收返回值

3.4 组合使用:生成真正的随机数

现在,我们可以将 srandtime 组合起来,让 rand 生成真正的随机数:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{
    // 使用time函数的返回值设置种子
    // 因为srand的参数是unsigned int类型,我们将time函数的返回值强制类型转换
    srand((unsigned int)time(NULL));
    
    printf("%d\n", rand());
    printf("%d\n", rand());
    printf("%d\n", rand());
    printf("%d\n", rand());
    printf("%d\n", rand());
    
    return 0;
}

多运行几次看看,每次的运行就有差异了。

⚠️ 注意: srand 函数是不需要频繁调用的,一次运行的程序中调用一次就够了

3.5 设置随机数的范围

在实际开发中,我们往往需要生成指定范围内的随机数。下面是一些常用的技巧:

目标范围 代码写法 原理说明
0 ~ 99 rand() % 100 余数的范围是0~99
1 ~ 100 rand() % 100 + 1 %100的余数是099,+1后范围是1100
100 ~ 200 100 + rand() % (200-100+1) 余数的范围是0100,加100后就是100200

通用公式: 要生成 a ~ b 的随机数,方法如下:

c 复制代码
a + rand() % (b - a + 1)

🎮 4. 猜数字游戏实现

掌握了随机数的生成方法,现在我们来完整实现猜数字游戏。

4.1 基础版本

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void game()
{
    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;
        }
    }
}

void menu()
{
    printf("***********************\n");
    printf("******  1. play  ******\n");
    printf("******  0. exit  ******\n");
    printf("***********************\n");
}

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;
}

4.2 进阶版本:增加次数限制

为了让游戏更有挑战性,我们可以加上猜数字的次数限制,如果5次猜不出来,就算失败。

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void game()
{
    int r = rand() % 100 + 1;
    int guess = 0;
    int count = 5;
    
    while (count)
    {
        printf("\n你还有%d次机会\n", count);
        printf("请猜数字>:");
        scanf("%d", &guess);
        
        if (guess < r)
        {
            printf("猜小了\n");
        }
        else if (guess > r)
        {
            printf("猜大了\n");
        }
        else
        {
            printf("恭喜你,猜对了\n");
            break;
        }
        count--;
    }
    
    if (count == 0)
    {
        printf("你失败了,正确值是:%d\n", r);
    }
}

void menu()
{
    printf("***********************\n");
    printf("******  1. play  ******\n");
    printf("******  0. exit  ******\n");
    printf("***********************\n");
}

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;
}

📝 5. 代码结构分析

让我们来梳理一下这个程序的整体结构:

5.1 函数模块化设计

函数名 功能 关键点
menu() 打印游戏菜单 纯输出,无参数无返回值
game() 核心游戏逻辑 生成随机数、循环判断、次数控制
main() 程序入口 初始化随机种子、菜单循环、分支选择

5.2 程序执行流程

#mermaid-svg-eD7n35enWwtRrtnL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-eD7n35enWwtRrtnL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-eD7n35enWwtRrtnL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-eD7n35enWwtRrtnL .error-icon{fill:#552222;}#mermaid-svg-eD7n35enWwtRrtnL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eD7n35enWwtRrtnL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-eD7n35enWwtRrtnL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eD7n35enWwtRrtnL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eD7n35enWwtRrtnL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-eD7n35enWwtRrtnL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eD7n35enWwtRrtnL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eD7n35enWwtRrtnL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eD7n35enWwtRrtnL .marker.cross{stroke:#333333;}#mermaid-svg-eD7n35enWwtRrtnL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eD7n35enWwtRrtnL p{margin:0;}#mermaid-svg-eD7n35enWwtRrtnL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-eD7n35enWwtRrtnL .cluster-label text{fill:#333;}#mermaid-svg-eD7n35enWwtRrtnL .cluster-label span{color:#333;}#mermaid-svg-eD7n35enWwtRrtnL .cluster-label span p{background-color:transparent;}#mermaid-svg-eD7n35enWwtRrtnL .label text,#mermaid-svg-eD7n35enWwtRrtnL span{fill:#333;color:#333;}#mermaid-svg-eD7n35enWwtRrtnL .node rect,#mermaid-svg-eD7n35enWwtRrtnL .node circle,#mermaid-svg-eD7n35enWwtRrtnL .node ellipse,#mermaid-svg-eD7n35enWwtRrtnL .node polygon,#mermaid-svg-eD7n35enWwtRrtnL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-eD7n35enWwtRrtnL .rough-node .label text,#mermaid-svg-eD7n35enWwtRrtnL .node .label text,#mermaid-svg-eD7n35enWwtRrtnL .image-shape .label,#mermaid-svg-eD7n35enWwtRrtnL .icon-shape .label{text-anchor:middle;}#mermaid-svg-eD7n35enWwtRrtnL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-eD7n35enWwtRrtnL .rough-node .label,#mermaid-svg-eD7n35enWwtRrtnL .node .label,#mermaid-svg-eD7n35enWwtRrtnL .image-shape .label,#mermaid-svg-eD7n35enWwtRrtnL .icon-shape .label{text-align:center;}#mermaid-svg-eD7n35enWwtRrtnL .node.clickable{cursor:pointer;}#mermaid-svg-eD7n35enWwtRrtnL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-eD7n35enWwtRrtnL .arrowheadPath{fill:#333333;}#mermaid-svg-eD7n35enWwtRrtnL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-eD7n35enWwtRrtnL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-eD7n35enWwtRrtnL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eD7n35enWwtRrtnL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-eD7n35enWwtRrtnL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eD7n35enWwtRrtnL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-eD7n35enWwtRrtnL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-eD7n35enWwtRrtnL .cluster text{fill:#333;}#mermaid-svg-eD7n35enWwtRrtnL .cluster span{color:#333;}#mermaid-svg-eD7n35enWwtRrtnL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-eD7n35enWwtRrtnL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-eD7n35enWwtRrtnL rect.text{fill:none;stroke-width:0;}#mermaid-svg-eD7n35enWwtRrtnL .icon-shape,#mermaid-svg-eD7n35enWwtRrtnL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eD7n35enWwtRrtnL .icon-shape p,#mermaid-svg-eD7n35enWwtRrtnL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-eD7n35enWwtRrtnL .icon-shape .label rect,#mermaid-svg-eD7n35enWwtRrtnL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eD7n35enWwtRrtnL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-eD7n35enWwtRrtnL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-eD7n35enWwtRrtnL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1
0
其他




程序启动
srand设置随机种子
显示菜单
用户选择
调用game()
退出程序
提示错误
猜对了吗?
次数用完?
提示大了/小了
显示正确答案
恭喜猜对
游戏结束

💡 6. 面试常见问题与解答

Q1:rand()srand() 的关系是什么?

解答: rand() 用于生成伪随机数,srand() 用于设置随机数生成器的种子。如果不调用 srand()rand() 默认使用种子1,导致每次程序运行生成的随机数序列相同。通过 srand() 设置不同的种子(通常使用 time(NULL) 返回的时间戳),可以让 rand() 每次生成不同的随机数序列。

Q2:为什么使用 time(NULL) 作为 srand() 的参数?

解答: time(NULL) 返回当前时间的时间戳(从1970年1月1日到现在的秒数),这个值每秒钟都在变化。用它作为种子,可以确保每次运行程序时种子都不同,从而生成不同的随机数序列。这是最简单且有效的获取变化种子的方法。

Q3:如何生成指定范围 a, b 的随机数?

解答: 使用公式 a + rand() % (b - a + 1)。原理是:rand() % (b - a + 1) 生成 0(b - a) 之间的随机数,再加上 a,范围就变成了 ab。例如生成1~100的随机数:1 + rand() % 100

Q4:猜数字游戏中,do-while 循环和 while 循环哪个更合适?为什么?

解答: do-while 循环更合适。因为游戏菜单至少需要显示一次让用户选择,do-while 保证循环体至少执行一次,符合"先显示菜单,再判断是否继续"的逻辑。如果使用 while 循环,需要在循环前额外显示一次菜单,代码会显得冗余。

Q5:在进阶版本中,如何实现"5次猜不对就失败"的逻辑?

解答: 使用一个计数器 count 初始化为5。每次循环 count--,当 count 变为0时循环结束。循环结束后判断 count == 0,如果是则说明用户没有在5次内猜对,输出失败信息。关键代码:

c 复制代码
int count = 5;
while (count) {
    // 猜数字逻辑
    count--;
}
if (count == 0) {
    printf("你失败了,正确值是:%d\n", r);
}

🏆 7. 总结

通过这个猜数字游戏项目,我们实践了以下重要知识点:

  1. 随机数生成 :掌握了 rand()srand()time() 的配合使用
  2. 循环结构 :综合运用了 while 循环和 do-while 循环
  3. 分支结构 :使用了 if-elseswitch-case 进行条件判断
  4. 模块化设计:将不同功能封装成独立的函数,提高代码可读性和复用性
  5. 程序流程控制 :通过 break 跳出循环,通过 continue(可扩展)控制循环节奏

建议:这个猜数小练习虽然简单,但它涵盖了C语言程序设计的核心思想。建议你亲手敲一遍代码,理解每一行代码的作用,这样才能在面试中游刃有余。