Nibbles Blog Nibbles Blog
  • 专题汇总
    • 技术学习
    • 个人成长
    • 运营技能
    • 自媒体
    • Minecraft
  • 推荐
    • 好物推荐
    • APP推荐
  • 关于我
    • Nibbles Studio
  • 友情链接
首页 › 技术学习 › 从猜数字程序来理解结构和随机数 | C语言基础

从猜数字程序来理解结构和随机数 | C语言基础

Avatar photo
Nibbles 尼布
3 5 月, 2025

由于博主过去学过Python,本篇文章中会有很多对照的分析。
本次为了更利于各部分的理解,我们从一个简单的猜数字程序的实现来说起…

1. 程序实现流程图:

从猜数字程序来理解结构和随机数 | C语言基础-Nibbles Blog

2. 随机数生成

Python中生成随机数的方式,可以利用内置的 random 模块进行生成。而且每次文件运行时输出的随机数都是不同的,同一文件内若多次调用随机数函数也都是不同的。

如下就是生成并打印1~100以内的整数的Python程序代码:

import random
randomNum = random.randint(1,100)
print(randomNum)

对于C语言中,如果要实现随机数,会稍显复杂。

首先,需要 rand() 函数实现伪随机数,为什么说是“伪”呢?是因为每次运行文件内的多个 rand() 函数时候之后的几次都和第一次是一个值。

从猜数字程序来理解结构和随机数 | C语言基础-Nibbles Blog

说明这样的“随机”有可能是经过某种算法得到的,而不是真的随机。调查后,事实上 rand() 函数每次都是对一个叫“种子”的基准值运算后生成的。那么我们就会想“种子”是如何得出的?根据什么来算出的?

根据此,可以想到用 srand() 函数来初始化随机数的生成器,通过 srand() 函数的 seed 参数来设置需要 rand() 函数生成随机数的“种子”。也就是说 srand() 的“种子”如果是随机的, rand() 就能生成随机数,但是生成随机数的前提是还需要一个随机的值,这样的矛盾又该如何解决呢?我们需要找到一个时刻在变的值来依据此得出这个需要的随机值。

那么我们可以使用程序运行的时间来作为“种子”,那么我们可以再引入 time() 函数。 time() 函数返回的类型是 time_t 类型(本质上还是32位或64位的整型)的时间戳值。时间戳是1970年1月1日0时0分0秒到程序运行时候的时间差,单位是秒。time() 函数中的参数 timer 是 NULL 即可返回差值。time_t 类型对于 srand() 函数是不接受的,所以强制类型转化 unsigned int 。

综上(其实上面的你没听懂也没关系),我们可以写出生成随机数的方式了。

注意需要新引入三个库:stdio.h 、 stdlib.h、 time.h

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(){
    srand((unsigned int)time(NULL));
    printf("%d\n", rand());
}

那么为了猜数字的范围缩小为1~100,我们可以对rand()函数结果对100取模。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(){
    srand((unsigned int)time(NULL));
    printf("%d\n", rand() % 100 +1);
}

3. 分支结构

3.1 Intro:关系操作符+逻辑操作符

  • 关系操作符
    • >(大于)
    • >=(大于等于)
    • <(小于)
    • <=(小于等于)
    • == (等于):特别注意这里是两个等号,在很多计算机语言中都是双等号表示“等于”(或是相同、相等)的判定符号
    • != (不等于)
  • 逻辑操作符
    • && (与):两边均为true才输出true,有一个false结果一定是false。(记忆方式:数字电路的方框形逻辑门中间的符号就是& )
      与逻辑示意图
    • || (或):两边其中一个为true就输出true,两边均为false才输出false。(记忆方式:数字电路的方框形逻辑门中间大于等于号下面是两条线的)
      或逻辑示意图

这些运算符的结果与算术运算符算出实际的结果不同,而是返回1(true)和0(false)整型值,以此来控制我们的程序接下来选择Y(Yes符合条件)的分支还是N(No不符合条件)的分支。

而且这些操作符都是双目的,也就是符号两边都需要有值。

注意与Python的符号表达不同的一点是,Python里 A<x<B 和 x>A and x<B 是实现的一个效果,都是把x值限定在(A,B)区间内,而C语言中对应的方式最好是 x>A && x<B,另一种形式( A<x<B )虽然不会报错,但对于C语言程序来说,执行时会有误解,实际上执行的是 (A<x)<B ,那么就是先判断A和x,然后才是把前面判断的结果和B再做判断,通常会达不到想要的结果。

3.2 if-else

#include <stdio.h>
int main()
{
    int age = 0;
    scanf("%d", &age);
    if (age >= 18) {
        printf("Adult\n");
    } else {
        printf("Child\n");
    }
}

如上是个很简单的年龄判断的 if-else 分支结构,结构也类似于Python里的。如果输入的age值是大于等于18就输出Adult,其他情况(也就是 age 值小于18)就输出Child。

那么还可以用嵌套的结构,能实现如果两个if条件+其他的三种分支。

#include <stdio.h>
int main()
{
    int score = 0;
    scanf("%d", &score);
    if (score >= 90) {
        printf("Great!\n");
    } else if (score >= 60) {
        printf("Pass\n");
    } else {
        printf("Failed\n");
    }
}

这里的 else if 就是类似于Python中的 elif ,这里其实可以看作是两个 if-else ,第一个结构中的 else 部分嵌套入了第二个结构。

如上,我们可以简单写出猜数字程序中的用分支结构表示的比大小的部分。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main() {
    srand((unsigned int)time(NULL));
    int randomNum = rand()%100+1;    // 随机数范围 1~100
    int num = 0;                   // 用户输入的猜测数
    printf("请输入 1-100 之间的数字:");
    scanf("%d", &num);
    if (randomNum > num) {
        printf("猜小了\n");
    }
    else if (randomNum == num) {
        printf("猜对啦\n");
    }
    else {
        printf("猜大了\n");
    }
    return 0;
}

需要提一下的是,C语言的 scanf() 虽然在功能上基本和Python中的 input() ,但是在输入的是字符串的情况下,不支持读取带空格的字符串。

3.3 条件操作符

还有种方式可以表达这样的分支结构,不过更多地是用于双分支或者三分支。但是三分支的实现如果要说代码的可读性上考虑,不太推荐使用。

基础的双分支的条件操作符的结构为:

逻辑或关系表达式 ? 真值表达式 : 假值表达式

就是那么简洁,一个问号和冒号就连接了三个表达式。来看极简的两数求最大的案例:

int max = (a > b) ? a : b;

那么我们上面的判断部分的如果要写成三分支(不推荐,为了清晰起见按行分层缩进了):

printf("%s\n",
       (randomNum > num)   ? "猜小了":
       (randomNum == num)  ? "猜对啦":
                           "猜大了");

双分支:

if (randomNum == num){
    printf("猜对啦");
} else {
    printf("%s\n", (randomNum > num) ? "猜小了" : "猜大了");
}

4. 循环结构

猜数字游戏中我们还希望做回合数( round )的限制,如果是猜错(即输出“猜大了”“猜小了”这两种)情况下,可以消耗回合数再猜一次。如果可猜的回合耗尽,则输出“GameOver”结束游戏。

4.1 while

C语言中非零即为true,零直接是false,可以借此作为循环次数的控制条件。设定好了我们总回合数,那么每执行完一次判断则可以直接减去一次回合数。当回合数为0正好可以结束循环条件。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main() {
    srand((unsigned int)time(NULL));
    int randomNum = rand() % 100 + 1;   // 随机数范围 1~100
    int num = 0;                      // 用户输入的猜测数
    int round = 5;                    // 最大回合数

    // while 循环控制回合,从 round 到 1
    while (round) {
        printf("请输入 1-100 之间的数字:");
        scanf("%d", &num);
        if (num == randomNum) {
            printf("猜对啦\n");
            break;
        } else if (num < randomNum) {
            printf("猜小了\n");
        } else {
            printf("猜大了\n");
        }
        round-=1;
    }
    if (round == 0) {
        printf("GameOver\n");
    }
    return 0;
}

不过,while循环一般更多用于未知循环次数的情况时,比如在刷题的时候经常会看到不知道会输入多少数代表输入终止(EOF)。

▼ 牛客网OJ上的初始代码

#include <stdio.h>
int main() {
&nbsp; &nbsp; int a, b;
&nbsp; &nbsp; while (scanf("%d %d", &a, &b) != EOF) { // 注意 while 处理多个 case
&nbsp; &nbsp; &nbsp; &nbsp; // 64 位输出请用 printf("%lld") to
&nbsp; &nbsp; &nbsp; &nbsp; printf("%d\n", a + b);
&nbsp; &nbsp; }
&nbsp; &nbsp; return 0;
}

4.2 for

for 循环就是常用于我们的已知循环次数的程序里了。

我们直接来看两个最常用的Python和C语言同样实现迭代的代码,这样看是不是好理解了。

for count in range(1, round + 1):
    print("这是第", count, "次循环")

Python中使用 range() 生成的整数序列作为可迭代对象,这个整数序列的区间是 [1, round] 末尾不取,函数默认的步长是1,实现效果就是把count变量的值不断从1取到round。

C语言中的对于起始值、终止条件、步长各部分用分号断开,这基本的三要素还是非常明显的。

for (int count = 1; count <= round; count++){
    printfprintf("这是第 %d 次循环\n", count);
}

那么我们猜数字程序用 for 循环就能这样写:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main() {
    srand((unsigned int)time(NULL));
    int randomNum = rand() % 100 + 1;   // 随机数范围 1~100
    int num = 0;                      // 用户输入的猜测数
    int round = 5;                    // 最大回合数

    // for 循环控制回合,从 1 到 round
    for (int count = 1; count <= round; count++) {
        printf("第 %d 轮,请输入 1-100 之间的数字:", count);
        scanf("%d", &num);
        if (num == randomNum) {
            printf("猜对啦\n");
            break;
        } else if (num < randomNum) {
            printf("猜小了\n");
        } else {
            printf("猜大了\n");
        }
        if (count == round) {
            printf("GameOver\n");
        }
    }
    return 0;
}

5. 补充的分支与循环结构

C语言中还有些别的分支和循环结构,接下来会一一道来。

5.1 switch 分支

switch 语句像一个分流路口:给它一个整数(或枚举、字符),它就把程序送到对应的 case 代码块,比长串 if‑else if 好读得多。

写法发生效果
switch (表达式)先算出表达式的值,再去找相同数字的 case。
case 常量:对上号就从这里开始执行。
default:前面都不对时走这里,可省略。
break;结束 switch,跳到大括号后面的第一条语句。
如果不写的话,

break 和 continue

  • break:立刻结束当前最近的一层 for、while 或 do‑while 循环或 switch 的条件分支,程序跳到循环后面的第一条语句。
  • continue:立刻结束本次迭代,把控制权送回循环头;for 会先执行增量表达式再检查条件,while/do‑while 会直接检查条件。在不套循环的 switch 里敲 continue,编译器会直接报错。
  • 在嵌套循环中,它们只作用于最内层。

只打印 1‑10 中的奇数,遇到 7 就提前停止:

#include <stdio.h>

int main() {
    for (int n = 1; n <= 10; ++n) {
        if (n == 7)
            break;          // 终止整个循环
        if (n % 2 == 0)
            continue;       // 跳过偶数,继续下一轮
        printf("%d ", n);
    }
    puts("循环结束");
    return 0;
}

//Output:
//1 3 5 
//循环结束
  • continue 让偶数分支直接回到 for 的自增步骤。
  • break 在遇到 7 时立即离开循环,剩余数字不再检查。

这样,你就能直观看到两条语句在循环里的“停一停”( continue )与“走人”( break )效果。

5.2 do‑while 循环

do‑while 像一次先行动、后条件判断的循环:不管条件真假,主体最少执行一次。

写法发生效果
do { ... } while (条件);先跑 do { ... },再判断 while (条件);条件为真就回到 do 继续,为假就结束循环。

例如,累加直到用户输入 0:

#include <stdio.h>

int main() {
    int n, sum = 0;
    do {
        printf("输入数字(0 结束): ");
        scanf("%d", &n);
        sum += n;
    } while (n != 0);
    printf("总和为 %d", sum);
    return 0;
}

PS:这并非是一定要使用do-while循环才能实现。

5.3 goto 不定转移

goto 是一句直接跳转:给它一个标签名,程序立刻冲过去执行标签下面的代码。

写法会发生效果
<label>:普通语句前加标签名,供 goto 跳入。
goto <label>;立即跳到同函数内的 label: 处。

比如下面这个分数输入的代码,遇到输入检查失败就跳到清理位置:

#include <stdio.h>

int main(void) {
    int score;
    printf("请输入分数(0~100): ");
    if (scanf("%d", &score) != 1)
        goto ERROR;
    if (score < 0 || score > 100)
        goto ERROR;
    printf("成绩合法,分数是 %d", score);
    return 0;

ERROR:                        
    printf("输入非法,程序结束。");
    return 1;
}

goto 语句如果使⽤的不当,就会导致在函数内部随意乱跳转,打乱程序的执⾏流程,所以我们的建议是能不⽤尽量不去使⽤;更多情况推荐 break、return 或把逻辑拆成函数更易读。

声明:本站原创文章文字版权归本站所有,转载务必注明作者和出处;本站转载文章仅仅代表原作者观点,不代表本站立场,图文版权归原作者所有。如有侵权,请联系我们删除。
C语言
1
0
Avatar photo
Nibbles 尼布
向着终生成长者迈进!
赞赏

评论 (0)

取消
    发表评论

猜你喜欢

  • 从本质理解指针的那些知识 | C语言基础
  • 谈谈printf()和scanf() | C语言基础
  • CTF Web 信息搜集 知识总结
Avatar photo

Nibbles 尼布

向着终生成长者迈进!
24
文章
4
评论
34
获赞

多平台更新

▲ “尼布是我”小程序
▲ 个人公众号

热门文章

TOP1
Python数据分析 Ⅰ:pandas模块
7 6 月, 2023
TOP2
更大挑战,更多机遇 | 2023 年终总结
8 1 月, 2024
TOP3
【杂谈】真的需要系统地学那么多编程语言吗?
14 8 月, 2024

标签云

AI (2) anaconda (1) Bilibili (1) Cookie (1) CS50x (1) CTF (1) C语言 (4) Git (1) Github (1) HTTP (1) Java (1) matplotlib module (1) Minecraft (2) NamelessMC (1) pandas module (1) PHP (1) python (2) Serverless (1) vim (1) VuePress (1) WordPress (1) zeabur (1) 公众号 (2) 年终总结 (2) 思维导图 (1) 换源 (1) 数据库 (1) 杂谈 (1) 浪前 (2) 自媒体 (2) 解密 (1) 运营 (2) 飞书 (1)

支持我!

Blog Circle

开往-友链接力
博客录(boke.lu) 随机博客
博客圈
  • SiteMap
  • Umami
  • xLog
  • Substack
Copyright © 2022-2025 Nibbles Blog. Designed by nicetheme. 浙公网安备33038102332481号 浙ICP备2024120131号
技术博客: AHdark Blog Tkong Blog iVampireSP 的物语 我不是咕咕鸽 热心市民 L 先生 ncc的个人网站 岚天小窝 天远笔记
  • C语言4
  • python2
  • AI2
  • 公众号2
  • 运营2
  • 专题汇总
    • 技术学习
    • 个人成长
    • 运营技能
    • 自媒体
    • Minecraft
  • 推荐
    • 好物推荐
    • APP推荐
  • 关于我
    • Nibbles Studio
  • 友情链接