C++ 控制流入门:if、switch、循环与提前返回
04 控制流:程序为什么走这条路
前面几章里的代码大多从上往下一路执行。真实程序很少只有一条直线——成绩及格时打印 passed,没及格时打印 failed;输入有效时继续处理,输入无效时提前返回;一组数据需要逐个遍历,循环会反复执行同一段逻辑。
这些改变执行路线的结构,统称为控制流。
控制流回答的是一个基础问题:程序为什么走这条路。
读控制流时,最稳的方法是把程序当成一条会分岔的路线。普通语句让程序继续往下走,if 让程序选择一条路,循环让程序回到某个检查点,break、continue、return 会直接改变下一站。只要能说清"下一条执行的语句在哪里",控制流就不会乱。
if 是最基础的岔路口
先看成绩判断:
#include <iostream>
int main() {
int score = 75;
if (score >= 60) {
std::cout << "passed\n";
} else {
std::cout << "failed\n";
}
return 0;
}
if 后面括号里的 score >= 60 是条件表达式,它会产生 bool 结果。结果为 true,程序执行第一个大括号里的语句;结果为 false,程序执行 else 后面的大括号。
这段控制流可以画成:
把条件结果先命名,读者能直接看到判断含义。条件复杂时,这种写法尤其清楚。
if 的质量主要取决于条件是否容易读。if (score >= 60) 很直接,读者能马上理解;条件变长以后,代码容易变成一串符号。把条件拆成 bool passed、bool input_valid 这种名字,本质上是给分支原因命名——程序走哪条路,读者能从名字里看出来。
多个分支可以用 else if:
if (score >= 90) {
std::cout << "excellent\n";
} else if (score >= 80) {
std::cout << "good\n";
} else if (score >= 60) {
std::cout << "passed\n";
} else {
std::cout << "failed\n";
}
这段代码按顺序检查条件。只要某个条件成立,执行对应分支后,后面的分支就会跳过。写这种区间判断时,顺序很重要——score >= 90 要放在 score >= 80 前面,因为 95 同时满足这两个条件,程序应该先进入更精确的等级。
多分支判断的顺序就是规则的一部分。代码会从上到下逐个检查条件,遇到第一个成立的分支就进入。写区间、权限、优先级时,顺序错误会让程序走错路。读 else if 链时,要按这个顺序在脑子里模拟一遍。
还有一个容易忽略的点:每个分支应该表达同一类判断。上面的例子里,所有条件都在判断分数等级,放在同一条 else if 链里很自然。如果一条链里先判断分数,再判断用户权限,又判断文件是否存在,读者会很难理解这些条件之间的优先级关系。控制流清楚,往往不是因为语法复杂,而是因为每条分支都在回答同一个问题。
switch 适合固定选项
当一个变量只会落在几个固定选项里,switch 读起来会比一串 if 更整齐。
#include <iostream>
int main() {
int command = 2;
switch (command) {
case 1:
std::cout << "start\n";
break;
case 2:
std::cout << "stop\n";
break;
default:
std::cout << "unknown\n";
break;
}
return 0;
}
switch 会查看 command 的值,然后跳到匹配的 case 标签。case 1、case 2 都是标签。default 处理没有匹配到任何 case 的情况。
break 用来跳出整个 switch。基础阶段,每个分支末尾都写清楚 break,读者就能确定每个分支的边界。省略 break 会继续往下执行后面的 case,这种行为叫 fallthrough——它有时有用,但第一阶段先避免。
可以把 switch 想成菜单:
后面讲枚举时,switch 会更自然。固定状态配合枚举值,代码能清楚表达"这个变量只允许落在这些状态里"。
switch 的优势在于边界整齐。一个变量对应几个固定值,每个值对应一段处理逻辑,读者可以快速扫完整张表。它不适合表达复杂条件组合,但很适合菜单、状态、命令这类固定集合。后面用 enum class 表达状态时,switch 会更有类型感。
读 switch 时,要把 case 当成标签,而不是当成独立的小函数。程序跳到某个 case 后,会继续往下执行,直到遇到 break、return 或整个 switch 结束。基础代码里每个分支写清楚 break,就是为了让标签边界和执行边界一致。以后看到刻意省略 break 的代码,要确认作者是否真的想让多个分支共享同一段逻辑。
for 循环的四段执行顺序
循环用来重复执行一段代码。for 很适合次数明确的循环:
#include <iostream>
int main() {
for (int i = 0; i < 5; ++i) {
std::cout << i << "\n";
}
return 0;
}
这段代码会输出 0 到 4。for 小括号里有三段:
int i = 0 // 初始化
i < 5 // 条件
++i // 更新
执行顺序是:
当条件变成 false,循环结束,程序继续执行 for 后面的语句。
for 循环最容易出错的地方是边界。i < 5 代表 0、1、2、3、4,一共 5 次;i <= 5 会多一次。写循环时,不要只盯着终止条件,还要把第一次和最后一次的 i 值说出来。能说出这两个值,循环边界通常就稳了。
很多循环错误都来自边界。比如想执行 5 次,常见写法是:
for (int i = 0; i < 5; ++i) {
std::cout << i << "\n";
}
i 的值是 0、1、2、3、4,一共 5 个。下标从 0 开始时,i < count 是非常常见的形式。
如果只是遍历一组值,后面学到 std::vector 以后,也会经常看到范围 for:
for (int score : scores) {
std::cout << score << "\n";
}
这类写法会在 auto 和小程序章节里展开。
for 的强项是把循环控制信息放在一行里——初始化、条件、更新都在小括号中,读者很容易看出循环变量从哪里开始、何时停止、每轮怎样变化。写 for 时,循环体最好专心处理"每一轮要做什么"。如果循环体里又频繁修改循环变量,读者就很难判断下一轮会走到哪里。
while 适合条件驱动的循环
for 常用于次数明确的场景。while 更适合循环次数由运行时条件决定的场景。
比如持续读取输入:
#include <iostream>
int main() {
int value = 0;
int sum = 0;
while (std::cin >> value) {
sum += value;
}
std::cout << "sum = " << sum << "\n";
return 0;
}
std::cin >> value 会尝试从输入读取一个整数。读取成功时,这个表达式可以用于条件判断,循环继续;读取失败时,条件结束,循环退出。
while 的执行顺序是:
条件一开始就是 false 时,循环体一次也不会执行。这个性质很重要——比如用户没有输入任何有效数字时,求和循环直接结束。
while 的重点是条件驱动。它不像 for 那样天然带一个计数变量,循环能跑几次往往取决于输入、状态或外部条件。读 while 时要先找这个条件什么时候会变化。如果循环体里没有任何东西能让条件变化,就要警惕死循环。
do while 则保证循环体至少执行一次:
int command = 0;
do {
std::cout << "1. start\n";
std::cout << "2. stop\n";
std::cin >> command;
} while (command != 2);
菜单场景里,先展示菜单再检查是否退出,do while 就比较自然。基础阶段掌握 while 和 for 已经够用,do while 看到时知道它先执行后判断即可。
选择循环结构时,可以先问循环的驱动力是什么。次数明确,通常用 for;条件来自输入或状态,通常用 while;必须先执行一次再判断,才考虑 do while。这不是硬性规则,但它能让代码读起来符合直觉——循环语句本身就在告诉读者:这个重复过程由什么控制。
break、continue、return 跳到不同位置
这三个语句都会改变执行路线,但跳转位置不同。
它们的共同点是打断当前顺序:break 打断循环或 switch,continue 打断本轮循环,return 打断整个函数。读代码时看到这三个词,要立刻抬头看它影响的是哪一层结构。嵌套越多,这个判断越重要。
break 跳出当前循环或 switch:
for (int i = 0; i < 10; ++i) {
if (i == 5) {
break;
}
std::cout << i << "\n";
}
这段代码输出 0 到 4。当 i == 5 时,break 直接结束循环。
continue 跳过本轮循环剩下的语句,进入下一轮:
for (int i = 0; i < 5; ++i) {
if (i == 2) {
continue;
}
std::cout << i << "\n";
}
这段代码输出 0、1、3、4。当 i == 2 时,continue 跳过输出语句,然后执行 ++i,进入下一轮判断。
return 结束当前函数:
int ClampScore(int score) {
if (score < 0) {
return 0;
}
if (score > 100) {
return 100;
}
return score;
}
一旦执行到 return,这个函数立刻结束,调用者会得到返回值。
三者可以这样对照:
读代码时看到这三个词,要马上问:它会跳到哪里。
这个问题在嵌套结构里尤其重要。break 只跳出它所在的那一层循环或 switch,不会自动跳出外层函数;continue 只进入当前循环的下一轮,不会跳过外层逻辑;return 直接结束当前函数,后面的语句都不会再执行。读到跳转语句时,先确定它影响哪一层,再继续往下读。
提前返回能减少嵌套
控制流写得清楚,程序会更容易读。处理错误输入时,提前返回是一种常见写法:
int NormalizeScore(int score) {
if (score < 0) {
return 0;
}
if (score > 100) {
return 100;
}
return score;
}
这段代码先处理边界情况,再处理正常情况。每个分支都很短。另一种写法是把所有逻辑塞进 else 里,层级会更深。
提前返回的价值在于把异常路径先处理掉,让主逻辑留在外层——函数一开始检查输入是否有效、列表是否为空、指针是否为空,失败就直接返回,剩下的代码就可以专心处理正常路径。基础阶段写小函数时,这种结构通常比层层嵌套更容易读。
基础阶段建议保持控制流直观:
先处理异常和边界
↓
再处理正常主线
↓
每个分支的大括号写完整
所有控制语句都写大括号。哪怕循环体或分支只有一行,也写大括号——这样以后添加第二行代码时,不容易引入缩进看起来对、实际绑定关系错的 bug。
提前返回也要克制。它适合处理明显的边界和失败路径,比如输入为空、分数越界、指针为空。正常主线被保留下来后,函数会变平。要是函数中间到处都有 return,读者反而要来回找所有出口。基础小函数里保持"前面处理边界,后面处理主线"这个结构,通常就足够清楚。
一段完整演示
下面的程序把分支、循环、跳转语句放在一起:
#include <iostream>
int NormalizeScore(int score) {
if (score < 0) {
return 0;
}
if (score > 100) {
return 100;
}
return score;
}
int main() {
int total = 0;
int count = 0;
for (int i = 0; i < 3; ++i) {
int score = 0;
std::cin >> score;
score = NormalizeScore(score);
total += score;
++count;
}
if (count == 0) {
std::cout << "no scores\n";
return 0;
}
int average = total / count;
switch (average / 10) {
case 10:
case 9:
std::cout << "excellent\n";
break;
case 8:
std::cout << "good\n";
break;
case 7:
case 6:
std::cout << "passed\n";
break;
default:
std::cout << "failed\n";
break;
}
return 0;
}
这段代码的主线是:读取 3 个分数,规范化每个分数,计算平均值,根据平均值输出等级。for 负责重复读取,if 负责边界判断,switch 负责固定等级分支,return 负责结束函数。
读控制流代码时,不需要一开始就陷进每个表达式。先抓路线:循环什么时候开始,什么时候结束;分支条件是什么;提前返回会不会跳过后续逻辑;固定状态是否适合用 switch。路线清楚以后,再看每条路上具体做了什么,阅读压力会小很多。
控制流能组织一段逻辑。随着逻辑变长,所有代码都堆在 main 里会越来越难读。下一章会讲函数:把一段逻辑装进一个有名字的单元,需要时调用它,让程序结构更清楚。
这一章真正要留下的读法是:每看到一个控制语句,就问"下一条可能执行的语句在哪里"。if 有两条路,switch 有多张标签,循环会回到条件检查,break、continue、return 会直接改写下一站。能把这条路线说出来,控制流就从一堆语法变成了可推理的程序路径。
阅读导航




