【C语言】从猜数字程序来理解分支循环结构和随机数

博客同步更新:https://nibbles.cn/c-branch-loop-structure.html
还不是个技术大牛,是个爱折腾的持续学习者。把这些笔记梳理成文,不仅是自己费曼,还是种互相学习的态度。


由于博主过去学过Python,本篇文章中会有很多对照的分析。

本次为了更利于各部分的理解,我们从一个简单的猜数字程序的实现来说起…

1. 程序实现流程图:

程序实现流程图

2. 随机数生成

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

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

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

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

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

伪随机数示范

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

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

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

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

注意需要新引入三个库:stdio.hstdlib.htime.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<Bx>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() {
    int a, b;
    while (scanf("%d %d", &a, &b) != EOF) { // 注意 while 处理多个 case
        // 64 位输出请用 printf("%lld") to
        printf("%d\n", a + b);
    }
    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++){
	printf("这是第 %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:立刻结束当前最近的一层 forwhiledo‑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 语句如果使⽤的不当,就会导致在函数内部随意乱跳转,打乱程序的执⾏流程,所以我们的建议是能不⽤尽量不去使⽤;更多情况推荐 breakreturn 或把逻辑拆成函数更易读。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NibblesCoding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值