学 c 语言的时候,最让人头秃的往往不是指针和内存——而是程序到底在按什么顺序执行。一行一行往下走?还是跳来跳去?还是反复转圈?
答案是:全靠控制流语句。c 语言的执行顺序由两大类语句决定:
1. 分支语句(branch):根据条件选择走哪条路——if、else、switch
2. 循环语句(loop):让代码反复执行——while、do-while、for
这篇文章把这两类语句讲透:语法结构是什么、执行流程长什么样、什么时候用哪个、有哪些坑要避。
一、整体概览:c 语言控制流家族
先看一张总图,建立全局印象:
flowchart td
a[控制流语句] --> b[分支语句]
a --> c[循环语句]
b --> b1[if / else if / else]
b --> b2[switch / case]
c --> c1[while]
c --> c2[do-while]
c --> c3[for]
图 1:c 语言控制流语句总览——左侧为分支语句族,右侧为循环语句族
从图中可以看到,分支语句负责"做选择",循环语句负责"重复做"。两者经常配合使用(比如在循环里用 if 判断是否跳出),是编写任何有逻辑的程序的基础。
二、分支语句:让程序学会做判断
2.1 if / else if / else —— 最常用的条件分支
if 是 c 语言中最基本的分支语句。它的逻辑很简单:如果条件成立,就执行某段代码;否则,执行另一段(或什么都不做)。
基本语法:
if (条件表达式) {
/* 条件为真时执行的代码 */
} else if (另一个条件) {
/* 上一个条件为假、这个条件为真时执行 */
} else {
/* 所有条件都为假时执行 */
}关键规则:
- 条件表达式必须是标量类型(整数、字符、指针),结果为
0表示假,非0表示真 - 花括号
{}:如果只有一条语句可以省略,但强烈建议永远不省(避免悬挂 else 等 bug) - else 总是与最近的未匹配的 if 配对——这是经典面试题
执行流程如下:
flowchart td
s[开始] --> c1{条件1}
c1 -->|真| a1[执行代码块1]
c1 -->|假| c2{条件2}
c2 -->|真| a2[执行代码块2]
c2 -->|假| c3{条件3}
c3 -->|真| a3[执行代码块3]
c3 -->|假| e[执行else代码块]
a1 --> end[结束]
a2 --> end
a3 --> end
e --> end
图 2:if-else-if 阶梯式条件判断的完整执行流程。程序从上到下依次检查每个条件,遇到第一个为真的就进入对应分支,之后的所有 else-if 和 else 都被跳过
一个实际例子——成绩等级判定:
int score = 85;
if (score >= 90) {
printf("优秀\n");
} else if (score >= 80) {
printf("良好\n"); /* score=85 走这里 */
} else if (score >= 60) {
printf("及格\n");
} else {
printf("不及格\n");
}2.2 switch-case —— 多路分支的利器
当你需要根据一个变量的值从很多选项中选一个时,写一长串 if-else if 既难看又低效。这时就该 switch 出场了。
switch (整型表达式) {
case 常量1:
语句组1;
break; /* 别忘了!*/
case 常量2:
语句组2;
break;
/* ... 更多 case ... */
default:
默认语句组; /* 所有 case 都不匹配时执行 */
}核心要点:
- 表达式必须是整数类型(
int、char、枚举),不能用浮点数或字符串 - case 后面的值必须是编译期常量,不能是变量
break的作用是跳出 switch——漏写会导致"穿透"(fall-through),继续执行下一个 case 的代码
flowchart td
s[计算表达式] --> c1{匹配case1?}
c1 -->|是| a1[执行语句组1] --> b1{有break?}
b1 -->|有| end[结束]
b1 -->|无| a2[执行语句组2]
c1 -->|否| c2{匹配case2?}
c2 -->|是| a2 --> b2{有break?}
b2 -->|有| end
b2 -->|无| a3[继续穿透...]
c2 -->|否| more{匹配更多case...}
more -->|不匹配| default[执行default] --> end
图 3:switch-case 多分支选择语句的执行流程。计算表达式的值后,跳转到匹配的 case 标签;break 负责跳出,缺少 break 会穿透到下一个 case
穿透(fall-through)有时是有意为之的技巧:
int day = 5;
switch (day) {
case 1: case 2: case 3: case 4: case 5:
printf("工作日\n");
break;
case 6: case 7:
printf("周末\n");
break;
}但更多时候它是 bug 的来源。建议:每个 case 都写 break,除非你明确需要穿透行为。
三、循环语句:让代码重复干活
3.1 while 循环 —— 先判断,后执行
while 是最简单的循环:只要条件为真,就反复执行循环体。
while (条件表达式) {
/* 循环体:条件为真时重复执行 */
}flowchart td
s[开始] --> c{条件为真?}
c -->|是| b[执行循环体] --> c
c -->|否| e[结束]
图 4:while 循环的执行流程。每次迭代都先检查条件,条件为真才进入循环体,条件为假立即退出
注意它的特点:条件一开始就是假的 → 循环体一次都不执行。这和 do-while 形成鲜明对比。
典型用法——计数循环:
int i = 0;
while (i < 5) {
printf("i = %d\n", i);
i++; /* 别忘了更新循环变量!否则死循环 */
}
/* 输出:i = 0, 1, 2, 3, 4 */3.2 do-while 循环 —— 先执行,后判断
do-while 和 while 几乎一样,只有一个关键区别:它至少执行一次循环体,然后再去判断条件。
do {
/* 循环体:至少执行一次 */
} while (条件表达式); /* 注意末尾的分号!*/flowchart td
subgraph while循环
w1[开始] --> w2{条件?}
w2 -->|真| w3[执行循环体] --> w2
w2 -->|假| w4[结束]
end
subgraph do-while循环
d1[开始] --> d2[执行循环体]
d2 --> d3{条件?}
d3 -->|真| d2
d3 -->|假| d4[结束]
end
图 5:do-while 与 while 的对比。左:while 先判断再执行(可能 0 次);右:do-while 先执行再判断(至少 1 次)。这是两者唯一但本质的区别
什么时候用 do-while?
- 菜单循环:先显示菜单,再问用户要不要继续
- 输入校验:先读取输入,再判断是否合法,不合法就重新输入
int choice;
do {
printf("===== 主菜单 =====\n");
printf("1. 新建文件\n");
printf("2. 打开文件\n");
printf("3. 退出\n");
printf("请选择:");
scanf("%d", &choice);
} while (choice < 1 || choice > 3); /* 输入不合法就重新显示菜单 */3.3 for 循环 —— 最强大的循环
for 循环把"初始化、条件判断、更新"三件事写在同一行,是 c 语言中使用频率最高的循环。
for (初始化表达式; 条件表达式; 更新表达式) {
/* 循环体 */
}flowchart td
init[初始化表达式] --> cond{条件表达式}
cond -->|真| body[执行循环体]
body --> update[更新表达式]
update --> cond
cond -->|假| end[结束]
图 6:for 循环的四部分结构解剖。①初始化只执行一次 → ②判断条件 → ③执行循环体 → ④执行更新表达式 → 回到②。三个表达式都可以省略(分号不能省)
等价的 while 写法:
/* for 版本 */
for (int i = 0; i < 10; i++) {
printf("%d ", i);
}
/* 等价 while 版本 */
int i = 0; /* 对应初始化 */
while (i < 10) { /* 对应条件 */
printf("%d ", i);
i++; /* 对应更新 */
}for 的灵活之处在于三个表达式都可以省略:
- 省略全部三个 →
for(;;)= 无限循环(需要break退出) - 省略条件 = 条件永远为真 = 无限循环
- 省略初始化 = 变量在外部已经定义好
常见模式速查:
| 模式 | 写法 | 用途 |
|---|---|---|
| 计数循环 | for(i=0; i<n; i++) | 遍历固定次数 |
| 倒序循环 | for(i=n-1; i>=0; i--) | 反向遍历 |
| 步进循环 | for(i=0; i<n; i+=2) | 每次步进 2 |
| 无限循环 | for(;;) | 事件监听/服务器 |
四、控制语句:break 与 continue
在循环内部,我们常常需要改变正常的执行流程。两个关键字承担了这个职责:
4.1 break —— 立即跳出
break 的作用是立即终止当前所在的循环或 switch,跳到后面的代码继续执行。
flowchart td
subgraph break
b1[循环执行中] --> b2{break?}
b2 -->|是| b3[跳出循环]
b2 -->|否| b4[继续执行]
end
subgraph continue
c1[循环执行中] --> c2{continue?}
c2 -->|是| c3[跳过本次剩余代码]
c3 --> c4[回到条件判断]
c2 -->|否| c5[继续执行]
end
图 7:break 与 continue 在循环中的行为对比。左:break 直接跳出整个循环,后续代码不再执行;右:continue 只跳过本次迭代的剩余部分,回到条件判断准备下一轮
/* 找到第一个能被 7 整除的数就停止 */
for (int i = 1; i <= 100; i++) {
if (i % 7 == 0) {
printf("找到:%d\n", i); /* 输出 7 */
break; /* 立刻退出循环 */
}
}
/* 循环结束后从这里继续 */4.2 continue —— 跳过本轮
continue 不退出循环,它只是跳过本次迭代中剩下的代码,直接回到循环的条件判断处,准备下一轮。
/* 打印 1~10 中所有奇数 */
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
continue; /* 偶数跳过,不打印 */
}
printf("%d ", i); /* 只有奇数能到这里 */
}
/* 输出:1 3 5 7 9 */记忆口诀:break 是"我不干了",continue 是"这次不算,下次再来"。
五、嵌套循环:外层走一步,内层跑一圈
当一个循环的循环体里又包含另一个循环时,就叫嵌套循环(nested loop)。这是打印九九乘法表、矩阵运算、图案输出等场景的核心技术。
flowchart td
outer[外层循环开始] --> outercond{外层条件}
outercond -->|真| inner[内层循环开始]
inner --> innercond{内层条件}
innercond -->|真| innerbody[执行内层循环体]
innerbody --> innerupdate[内层更新] --> innercond
innercond -->|假| outerupdate[外层更新] --> outercond
outercond -->|假| end[结束]
图 8:嵌套循环的执行顺序。外层每推进一轮,内层都要从头到尾完整执行一遍。总执行次数 = 外层迭代次数 × 内层迭代次数
经典案例——九九乘法表:
for (int i = 1; i <= 9; i++) { /* 外层:行 1~9 */
for (int j = 1; j <= i; j++) { /* 内层:列 1~i */
printf("%dx%d=%-4d", j, i, i * j);
}
printf("\n"); /* 每行结束后换行 */
}输出:
1x1=1 1x2=2 2x2=4 1x3=3 2x3=6 3x3=9 ...(共 9 行,第 n 行有 n 个式子)
嵌套循环的性能注意点:
- 双层嵌套的时间复杂度通常是 o(n²),数据量大时要警惕
- 能用单层循环解决的问题不要用嵌套(比如把二维数组展平成一维)
break只跳出最近的一层循环,不会同时跳出所有层
六、实战练习:综合运用
下面是一个结合了分支、循环和控制语句的完整示例——猜数字游戏:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
srand((unsigned int)time(null));
int secret = rand() % 100 + 1; /* 1~100 的随机数 */
int guess, count = 0;
printf("=== 猜数字游戏 ===\n");
printf("我想了一个 1~100 之间的数字,你来猜!\n\n");
do {
printf("请输入你的猜测(1~100):");
scanf("%d", &guess);
count++;
if (guess < 1 || guess > 100) {
printf("范围不对!请输入 1~100 之间的数。\n\n");
continue; /* 跳过,重新输入 */
}
if (guess > secret) {
printf("太大了!再试试。\n\n");
} else if (guess < secret) {
printf("太小了!再试试。\n\n");
} else {
printf("\n恭喜你猜对了!答案就是 %d\n", secret);
printf("你一共猜了 %d 次。\n", count);
break; /* 猜对了,退出循环 */
}
if (count >= 10) {
printf("你已经猜了 10 次,游戏结束。\n");
printf("正确答案是 %d\n", secret);
break;
}
} while (1); /* 无限循环,靠 break 退出 */
return 0;
}这个小程序用到了本文讲到的所有知识点:
do-while:保证至少运行一次if / else if / else:多路分支判断大小关系continue:跳过非法输入break:两种退出方式(猜对 / 超过次数)rand()+%:生成随机数
七、总结与注意事项
| 语句 | 类型 | 核心特点 | 典型场景 |
|---|---|---|---|
if / else | 分支 | 先判断后执行,可嵌套 | 条件判断、范围检查 |
switch / case | 分支 | 整数值多路跳转 | 菜单选择、状态机 |
while | 循环 | 先判断后执行,可能 0 次 | 不确定次数的循环 |
do-while | 循环 | 先执行后判断,至少 1 次 | 菜单循环、输入校验 |
for | 循环 | 三合一(初始/条件/更新) | 固定次数遍历 |
break | 控制 | 跳出整个循环/switch | 找到目标后退出 |
continue | 控制 | 跳过本次剩余代码 | 过滤不需要的数据 |
新手最容易犯的错:
if (x = 5)—— 赋值号写成比较号(应该==),永远为真switch里忘记写break—— 导致意外的穿透do-while忘记末尾分号 —— 编译错误- 循环体内忘记更新循环变量 —— 死循环
for循环的三个表达式之间用逗号而不是分号 —— 语法错误
掌握了分支和循环,你就掌握了 c 语言程序的"骨架"。接下来可以挑战更复杂的组合应用:用嵌套循环打印各种图案、用 switch 实现简单计算器、用 break + 标签实现多层跳出。多写代码、多画流程图,这些概念很快就会变成你的肌肉记忆。
以上就是c语言分支与循环语句的完全指南的详细内容,更多关于c语言分支与循环语句的资料请关注代码网其它相关文章!
发表评论