博客同步更新: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()
函数中的参数 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() {
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:立刻结束当前最近的一层
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
或把逻辑拆成函数更易读。