流程语句
# 第 6 章 C++ 流程语句
# 目录介绍
# 6.1 程序流程结构
C/C++支持最基本的三种程序运行结构:==顺序结构、选择结构、循环结构==
- 顺序结构:程序按顺序执行,不发生跳转
- 选择结构:依据条件是否满足,有选择的执行相应功能
- 循环结构:依据条件是否满足,循环多次执行某段代码
# 6.1.1 综合案例与思考
综合案例:三种流程结构综合演示
#include <iostream>
using namespace std;
int main() {
// 1. 顺序结构:按顺序执行
cout << "=== 顺序结构 ===" << endl;
int a = 10;
int b = 20;
int sum = a + b;
cout << "a + b = " << sum << endl; // 按顺序:声明->计算->输出
// 2. 选择结构:根据条件分支
cout << "\n=== 选择结构 ===" << endl;
if (sum > 25) {
cout << "sum大于25" << endl;
} else {
cout << "sum不大于25" << endl;
}
// 3. 循环结构:重复执行
cout << "\n=== 循环结构 ===" << endl;
int total = 0;
for (int i = 1; i <= 10; ++i) {
total += i;
}
cout << "1到10的和: " << total << endl;
// 综合:用三种结构实现猜数字(简化版)
cout << "\n=== 综合应用 ===" << endl;
int secret = 7; // 顺序:设定目标
int guess = 1;
while (guess <= 10) { // 循环:逐个尝试
if (guess == secret) { // 选择:判断是否猜中
cout << "猜中了!数字是 " << guess << endl;
break;
}
guess++;
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
案例知识融合:这个案例先分别演示三种基本流程结构(顺序执行→条件分支→循环迭代),然后用一个"猜数字"的综合示例展示三种结构如何协作——顺序初始化数据、循环逐个尝试、选择判断结果。
思考题:
- 理论上只用"顺序"和"选择"就能实现循环(通过goto或递归),那为什么还需要专门的循环结构?
- 任何程序都可以用这三种基本结构实现(结构化定理),你能解释为什么吗?
- 函数调用也会改变执行流程,它属于三种基本结构中的哪一种?还是一种独立的流程控制方式?
# 6.2 选择结构
# 6.2.1 if语句
作用:执行满足条件的语句
if语句的三种形式
- 单行格式if语句
- 多行格式if语句
- 多条件的if语句
# 6.2.2 单行格式if语句
单行格式if语句:if(条件){ 条件满足执行的语句 }
示例:
int main() {
//选择结构-单行if语句
//输入一个分数,如果分数大于600分,视为考上一本大学,并在屏幕上打印
int score = 0;
cout << "请输入一个分数:" << endl;
cin >> score;
cout << "您输入的分数为: " << score << endl;
//if语句
//注意事项,在if判断语句后面,不要加分号
if (score > 600) {
cout << "我考上了一本大学!!!" << endl;
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
注意:if条件表达式后不要加分号
# 6.2.3 if-else语句
多行格式if语句:if(条件){ 条件满足执行的语句 }else{ 条件不满足执行的语句 };
示例:
int main() {
int score = 0;
cout << "请输入考试分数:" << endl;
cin >> score;
if (score > 600) {
cout << "我考上了一本大学" << endl;
} else {
cout << "我未考上一本大学" << endl;
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
# 6.2.4 多条件的if语句
多条件的if语句:if(条件1){ 条件1满足执行的语句 }else if(条件2){条件2满足执行的语句}... else{ 都不满足执行的语句}
示例:
int main() {
int score = 0;
cout << "请输入考试分数:" << endl;
cin >> score;
if (score > 600) {
cout << "我考上了一本大学" << endl;
} else if (score > 500) {
cout << "我考上了二本大学" << endl;
} else if (score > 400) {
cout << "我考上了三本大学" << endl;
} else {
cout << "我未考上本科" << endl;
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 6.2.5 嵌套if语句
嵌套if语句:在if语句中,可以嵌套使用if语句,达到更精确的条件判断
案例需求:
- 提示用户输入一个高考考试分数,根据分数做如下判断
- 分数如果大于600分视为考上一本,大于500分考上二本,大于400考上三本,其余视为未考上本科;
- 在一本分数中,如果大于700分,考入北大,大于650分,考入清华,大于600考入人大。
示例:
int main() {
int score = 0;
cout << "请输入考试分数:" << endl;
cin >> score;
if (score > 600) {
cout << "我考上了一本大学" << endl;
if (score > 700) {
cout << "我考上了北大" << endl;
} else if (score > 650) {
cout << "我考上了清华" << endl;
} else {
cout << "我考上了人大" << endl;
}
} else if (score > 500) {
cout << "我考上了二本大学" << endl;
} else if (score > 400) {
cout << "我考上了三本大学" << endl;
} else {
cout << "我未考上本科" << endl;
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 6.2.6 三目运算符
作用: 通过三目运算符实现简单的判断
语法:表达式1 ? 表达式2 :表达式3
解释:
如果表达式1的值为真,执行表达式2,并返回表达式2的结果;
如果表达式1的值为假,执行表达式3,并返回表达式3的结果。
示例:
int main() {
int a = 10;
int b = 20;
int c = 0;
c = a > b ? a : b;
cout << "c = " << c << endl;
//C++中三目运算符返回的是变量,可以继续赋值
(a > b ? a : b) = 100;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
总结:和if语句比较,三目运算符优点是短小整洁,缺点是如果用嵌套,结构不清晰
# 6.2.7 switch语句
作用:执行多条件分支语句
语法:
switch(表达式){
case 结果1:执行语句;break;
case 结果2:执行语句;break;
...
default:执行语句;break;
}
2
3
4
5
6
示例:
int main() {
//请给电影评分
//10 ~ 9 经典
// 8 ~ 7 非常好
// 6 ~ 5 一般
// 5分以下 烂片
int score = 0;
cout << "请给电影打分" << endl;
cin >> score;
switch (score) {
case 10:
case 9:
cout << "经典" << endl;
break;
case 8:
cout << "非常好" << endl;
break;
case 7:
case 6:
cout << "一般" << endl;
break;
default:
cout << "烂片" << endl;
break;
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
注意1:switch语句中表达式类型只能是整型或者字符型
注意2:case里如果没有break,那么程序会一直向下执行
总结:与if语句比,对于多条件判断时,switch的结构清晰,执行效率高,缺点是switch不可以判断区间
# 6.2.8 选择结构底层原理
if-else 的汇编翻译:编译器将 if-else 翻译成条件跳转指令。CPU 先用 CMP 指令做比较(本质是减法),然后根据标志寄存器的值决定是否跳转。
; if (x > 10) 的汇编伪码
mov eax, [x] ; 加载x的值
cmp eax, 10 ; 比较x和10(执行 x - 10)
jle ELSE_BRANCH ; 如果 x <= 10,跳转到else分支
; ... if分支的代码 ...
jmp END_IF ; 跳过else分支
ELSE_BRANCH:
; ... else分支的代码 ...
END_IF:
2
3
4
5
6
7
8
9
疑惑:if-else 和 switch 在底层有什么区别?switch 真的更快吗?
答疑:对于少量分支(2-3个),if-else 和 switch 性能几乎没有区别,编译器可能生成相同的代码。但当 case 数量较多且值连续时,编译器会将 switch 优化为跳转表(Jump Table)。
论证:跳转表的原理是用 case 值作为数组索引,直接查表跳转,时间复杂度是 O(1),而 if-else 链是逐个比较,最坏 O(n)。
; switch 跳转表伪码(case 0, 1, 2, 3)
mov eax, [choice] ; 加载switch变量
cmp eax, 3 ; 检查是否超出范围
ja DEFAULT ; 超出则跳转到default
jmp [JUMP_TABLE + eax*8] ; 直接查表跳转!O(1)
JUMP_TABLE:
dq CASE_0, CASE_1, CASE_2, CASE_3 ; 跳转地址表
2
3
4
5
6
7
8
结果展示:当 case 值稀疏(如 1, 100, 10000)时,编译器不会生成跳转表(太浪费内存),而是用二分查找或退化为 if-else 链。所以 switch 的性能优势取决于 case 值的分布。你可以用 g++ -S 生成汇编代码来验证。
三目运算符的底层优化:
三目运算符 a ? b : c 在底层和 if-else 是等价的,编译器会生成相同的条件跳转。但现代 CPU 有一个特殊指令——条件移动(CMOVcc),可以在不跳转的情况下完成选择:
; int result = (x > 0) ? x : -x;
mov eax, [x]
mov ebx, eax
neg ebx ; ebx = -x
cmp eax, 0
cmovle eax, ebx ; 如果 x <= 0,则 eax = -x(无跳转!)
2
3
4
5
6
条件移动避免了分支预测失败的惩罚(现代CPU中,分支预测失败可能浪费10-20个时钟周期),因此在某些性能关键路径中,三目运算符可能比 if-else 更快。
# 6.2.9 选择结构训练题
训练1:编写一个程序,使用 if-else 实现一个分数等级转换器。输入分数(0-100),输出等级(A/B/C/D/F),并统计各等级的分数段:
#include <iostream>
using namespace std;
int main() {
int scores[] = {95, 87, 72, 63, 45, 88, 91, 55, 76, 82};
int countA = 0, countB = 0, countC = 0, countD = 0, countF = 0;
for (int score : scores) {
char grade;
if (score >= 90) { grade = 'A'; countA++; }
else if (score >= 80) { grade = 'B'; countB++; }
else if (score >= 70) { grade = 'C'; countC++; }
else if (score >= 60) { grade = 'D'; countD++; }
else { grade = 'F'; countF++; }
cout << score << " -> " << grade << endl;
}
cout << "\n统计: A=" << countA << " B=" << countB
<< " C=" << countC << " D=" << countD << " F=" << countF << endl;
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
然后思考:如果把 if-else 链改成 switch(switch(score / 10)),代码会更简洁吗?性能会更好吗?
训练2:编写一个简易命令行计算器,使用 switch 处理运算符:
#include <iostream>
using namespace std;
int main() {
double num1 = 10, num2 = 3;
char ops[] = {'+', '-', '*', '/', '%'};
for (char op : ops) {
cout << num1 << " " << op << " " << num2 << " = ";
switch (op) {
case '+': cout << num1 + num2; break;
case '-': cout << num1 - num2; break;
case '*': cout << num1 * num2; break;
case '/':
if (num2 != 0) cout << num1 / num2;
else cout << "除零错误";
break;
case '%':
cout << static_cast<int>(num1) % static_cast<int>(num2);
break;
default: cout << "不支持的运算符";
}
cout << endl;
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
思考:如果不写 break,会发生 fall-through。C++17 引入了 [[fallthrough]] 属性,它的作用是什么?
训练3:比较 if-else 和 switch 的编译结果。编写以下两段代码,用 g++ -S -O2 生成汇编,观察差异:
// 版本A:if-else
int classify_if(int x) {
if (x == 0) return 10;
else if (x == 1) return 20;
else if (x == 2) return 30;
else if (x == 3) return 40;
else return 50;
}
// 版本B:switch
int classify_switch(int x) {
switch (x) {
case 0: return 10;
case 1: return 20;
case 2: return 30;
case 3: return 40;
default: return 50;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
观察:编译器在 -O2 优化下,是否对两者生成了相同的代码?switch 版本是否使用了跳转表?
# 6.2.10 综合案例与思考
综合案例:用选择结构实现简易菜单系统
#include <iostream>
#include <string>
using namespace std;
int main() {
int choice;
cout << "======= 学生管理系统 =======" << endl;
cout << "1. 查看成绩" << endl;
cout << "2. 修改成绩" << endl;
cout << "3. 成绩统计" << endl;
cout << "4. 退出系统" << endl;
cout << "请选择(1-4): ";
cin >> choice;
// if-else多条件判断
if (choice == 1) {
cout << "--- 成绩列表 ---" << endl;
int scores[] = {85, 92, 78, 96};
string names[] = {"张三", "李四", "王五", "赵六"};
for (int i = 0; i < 4; ++i) {
string level;
// 嵌套if:成绩等级判断
if (scores[i] >= 90) level = "优秀";
else if (scores[i] >= 80) level = "良好";
else if (scores[i] >= 60) level = "及格";
else level = "不及格";
cout << names[i] << ": " << scores[i] << " (" << level << ")" << endl;
}
} else if (choice == 2) {
cout << "请输入学生编号和新成绩: ";
// 三目运算符:快速判断
int id, newScore;
cin >> id >> newScore;
string result = (newScore >= 0 && newScore <= 100) ? "修改成功" : "分数无效";
cout << result << endl;
} else if (choice == 3) {
// switch语句:统计方式选择
cout << "统计方式: 1.平均分 2.最高分 3.最低分" << endl;
int mode;
cin >> mode;
int scores[] = {85, 92, 78, 96};
switch (mode) {
case 1: {
int sum = 0;
for (int s : scores) sum += s;
cout << "平均分: " << sum / 4.0 << endl;
break;
}
case 2:
cout << "最高分: 96" << endl;
break;
case 3:
cout << "最低分: 78" << endl;
break;
default:
cout << "无效选择" << endl;
}
} else {
cout << "退出系统,再见!" << endl;
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
案例知识融合:这个案例通过一个菜单系统综合使用了所有选择结构——if-else多条件分支(菜单选择)、嵌套if(成绩等级判断)、三目运算符(快速校验)、switch语句(统计方式选择)。展示了不同选择结构各自最适合的使用场景。
思考题:
- if-else链和switch语句都能实现多分支选择,它们在性能上有差异吗?编译器对switch做了什么优化?
- C++17引入了
if语句的初始化器(如if (int x = getValue(); x > 0)),这种语法有什么好处? - 在实际项目中,当分支非常多时(如几十个case),除了switch还有什么更好的设计方式?(提示:考虑函数指针表或策略模式)
# 6.3 循环结构
# 6.3.1 while循环语句
作用:满足循环条件,执行循环语句
语法:while(循环条件){ 循环语句 }
解释:==只要循环条件的结果为真,就执行循环语句==
示例:
int main() {
// 局部变量声明
int a = 10;
// while 循环执行
while (a < 20) {
cout << "a 的值:" << a << endl;
a++;
}
return 0;
}
2
3
4
5
6
7
8
9
10
注意:在执行循环语句时候,程序必须提供跳出循环的出口,否则出现死循环
# 6.3.2 do...while循环语句
作用:满足循环条件,执行循环语句
语法:do{ 循环语句 } while(循环条件);
注意:与while的区别在于==do...while会先执行一次循环语句==,再判断循环条件
示例:
int main() {
// 局部变量声明
int a = 10;
// do 循环执行
do {
cout << "a 的值:" << a << endl;
a = a + 1;
} while (a < 20);
return 0;
}
2
3
4
5
6
7
8
9
10
总结:与while循环区别在于,do...while先执行一次循环语句,再判断循环条件
# 6.3.3 for循环语句
作用: 满足循环条件,执行循环语句
语法:for(起始表达式;条件表达式;末尾循环体) { 循环语句; }
示例:
int main() {
// for 循环执行
for (int a = 10; a < 20; a = a + 1) {
cout << "a 的值:" << a << endl;
}
return 0;
}
2
3
4
5
6
7
注意:for循环中的表达式,要用分号进行分隔
总结:while , do...while, for都是开发中常用的循环语句,for循环结构比较清晰,比较常用
# 6.3.4 嵌套循环
作用: 在循环体中再嵌套一层循环,解决一些实际问题
示例:
int main() {
//外层循环执行1次,内层循环执行1轮
for (int i = 0; i < 10; i++){
for (int j = 0; j < 10; j++){
cout << "*" << " ";
}
cout << endl;
}
return 0;
}
2
3
4
5
6
7
8
9
10
# 6.3.5 循环结构底层原理
循环的汇编本质:所有循环在底层都是 条件跳转 + 无条件跳转 的组合。CPU 没有"循环"的概念,只有"跳转"。
while 循环的汇编翻译:
; while (i < n) { body; i++; }
LOOP_START:
cmp [i], [n] ; 比较 i 和 n
jge LOOP_END ; 如果 i >= n,跳出循环
; ... 循环体代码 ...
inc [i] ; i++
jmp LOOP_START ; 无条件跳回循环开始
LOOP_END:
2
3
4
5
6
7
8
for 循环和 while 循环在底层完全等价:for (init; cond; step) { body; } 等价于 init; while (cond) { body; step; }。编译器生成相同的机器码。
do-while 循环的特殊优化:do-while 的汇编比 while 少一条跳转指令,因为它把条件判断放在循环末尾:
; do { body; i++; } while (i < n);
LOOP_START:
; ... 循环体代码 ...
inc [i] ; i++
cmp [i], [n] ; 比较
jl LOOP_START ; 如果 i < n,跳回循环开始
; 注意:没有开头的无条件跳转!
2
3
4
5
6
7
疑惑:编译器的循环优化有多强?
答疑:现代编译器(GCC/Clang/MSVC)在 -O2 以上会进行多种循环优化,其效果可能超出你的想象。
论证:常见的循环优化技术包括:
- 循环展开(Loop Unrolling):将循环体复制多次,减少循环开销
// 原始代码
for (int i = 0; i < 100; i++) {
arr[i] = arr[i] * 2;
}
// 编译器展开后(伪代码)
for (int i = 0; i < 100; i += 4) {
arr[i] = arr[i] * 2;
arr[i+1] = arr[i+1] * 2;
arr[i+2] = arr[i+2] * 2;
arr[i+3] = arr[i+3] * 2;
}
2
3
4
5
6
7
8
9
10
11
12
循环不变量外提(Loop-Invariant Code Motion):将循环中不变的计算移到循环外
强度削减(Strength Reduction):将循环中的乘法替换为加法
// 原始代码
for (int i = 0; i < n; i++) {
arr[i * 4] = 0; // 每次乘法
}
// 优化后
int offset = 0;
for (int i = 0; i < n; i++) {
arr[offset] = 0; // 加法替代乘法
offset += 4;
}
2
3
4
5
6
7
8
9
10
11
- 自动向量化(Auto-Vectorization):利用 SIMD 指令一次处理多个数据
结果展示:你可以用 g++ -O2 -fopt-info-vec-optimized 查看编译器的向量化报告,了解哪些循环被自动优化了。在某些场景中,编译器优化后的代码甚至比手写汇编更快,因为编译器能全局分析代码的数据依赖关系。
# 6.3.6 循环结构训练题
训练1:使用三种循环分别计算斐波那契数列的前20项,对比代码风格:
#include <iostream>
using namespace std;
int main() {
// while版本
cout << "=== while ===" << endl;
int a = 0, b = 1, count = 0;
while (count < 20) {
cout << a << " ";
int temp = a + b;
a = b;
b = temp;
count++;
}
cout << endl;
// for版本
cout << "=== for ===" << endl;
a = 0; b = 1;
for (int i = 0; i < 20; i++) {
cout << a << " ";
int temp = a + b;
a = b;
b = temp;
}
cout << endl;
// do-while版本
cout << "=== do-while ===" << endl;
a = 0; b = 1; count = 0;
do {
cout << a << " ";
int temp = a + b;
a = b;
b = temp;
count++;
} while (count < 20);
cout << endl;
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
思考:三种写法产生完全相同的输出,但哪种最适合这个场景?为什么?
训练2:用嵌套循环打印杨辉三角的前10行:
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
const int rows = 10;
int triangle[rows][rows] = {0};
for (int i = 0; i < rows; i++) {
triangle[i][0] = 1; // 每行第一个元素为1
triangle[i][i] = 1; // 每行最后一个元素为1
for (int j = 1; j < i; j++) {
triangle[i][j] = triangle[i-1][j-1] + triangle[i-1][j];
}
}
// 格式化输出
for (int i = 0; i < rows; i++) {
cout << string((rows - i) * 2, ' '); // 前导空格
for (int j = 0; j <= i; j++) {
cout << setw(4) << triangle[i][j];
}
cout << endl;
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
思考:这段代码的时间复杂度是多少?如果只需要第n行的值,能否不用二维数组?
训练3:编写一个判断素数的程序,并用循环优化找出1-1000内的所有素数:
#include <iostream>
#include <cmath>
using namespace std;
bool isPrime(int n) {
if (n < 2) return false;
if (n < 4) return true;
if (n % 2 == 0 || n % 3 == 0) return false;
// 优化:只检查 6k±1 形式的因子
for (int i = 5; i <= sqrt(n); i += 6) {
if (n % i == 0 || n % (i + 2) == 0)
return false;
}
return true;
}
int main() {
int count = 0;
for (int i = 2; i <= 1000; i++) {
if (isPrime(i)) {
cout << i << " ";
count++;
}
}
cout << "\n共 " << count << " 个素数" << endl;
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
思考:isPrime 中为什么只需要检查到 sqrt(n)?6k±1 优化的原理是什么?
# 6.3.7 综合案例与思考
综合案例:用循环解决实际问题集
#include <iostream>
#include <cmath>
using namespace std;
int main() {
// 1. while循环:输入验证(至少执行到输入合法)
cout << "=== while: 输入验证 ===" << endl;
int input;
cout << "请输入1-100的数字: ";
cin >> input;
while (input < 1 || input > 100) {
cout << "输入无效,请重新输入: ";
cin >> input;
}
cout << "你输入了: " << input << endl;
// 2. do-while循环:至少执行一次的菜单
cout << "\n=== do-while: 菜单循环 ===" << endl;
int choice;
do {
cout << "1.计算 2.查询 0.退出 >> ";
cin >> choice;
if (choice == 1) cout << "执行计算..." << endl;
else if (choice == 2) cout << "执行查询..." << endl;
} while (choice != 0);
cout << "已退出" << endl;
// 3. for循环:经典数学问题
cout << "\n=== for: 求阶乘 ===" << endl;
int n = 10;
long long factorial = 1;
for (int i = 1; i <= n; ++i) {
factorial *= i;
}
cout << n << "! = " << factorial << endl;
// 4. 范围for循环(C++11)
cout << "\n=== 范围for ===" << endl;
int arr[] = {3, 1, 4, 1, 5, 9};
int maxVal = arr[0];
for (int val : arr) {
if (val > maxVal) maxVal = val;
}
cout << "最大值: " << maxVal << endl;
// 5. 嵌套循环:九九乘法表
cout << "\n=== 嵌套循环: 乘法表 ===" << endl;
for (int i = 1; i <= 9; ++i) {
for (int j = 1; j <= i; ++j) {
cout << j << "×" << i << "=" << i * j << "\t";
}
cout << endl;
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
案例知识融合:这个案例展示了每种循环的最佳使用场景——while用于"先判断后执行"的输入验证、do-while用于"至少执行一次"的菜单循环、for用于已知次数的数学计算、范围for用于遍历容器/数组、嵌套循环用于二维问题(九九乘法表)。
思考题:
while(true)无限循环+内部break退出 vsdo-while(condition)循环,你更倾向于用哪种方式?各有什么优缺点?- C++11的范围for循环(
for (auto x : container))和传统for循环相比有什么优势?什么场景下不能使用范围for? - 循环中应该把不变的计算放在循环外(循环不变量外提),编译器的
-O2优化能自动做到这点吗?
# 6.4 跳转语句
# 6.4.1 break语句
作用: 用于跳出==选择结构==或者==循环结构==
break使用的时机:
- 出现在switch条件语句中,作用是终止case并跳出switch
- 出现在循环语句中,作用是跳出当前的循环语句
- 出现在嵌套循环中,跳出最近的内层循环语句
示例1:在switch 语句中使用break
int main() {
cout << "请选择您挑战副本的难度:" << endl;
cout << "1、普通" << endl;
cout << "2、中等" << endl;
cout << "3、困难" << endl;
int num = 0;
cin >> num;
switch (num) {
case 1:
cout << "您选择的是普通难度" << endl;
break;
case 2:
cout << "您选择的是中等难度" << endl;
break;
case 3:
cout << "您选择的是困难难度" << endl;
break;
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
示例2:在循环语句中用break
int main() {
for (int i = 0; i < 10; i++) {
if (i == 5) {
break; //跳出循环语句
}
cout << i << endl;
}
return 0;
}
2
3
4
5
6
7
8
9
示例3:在嵌套循环语句中使用break,退出内层循环
int main() {
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
if (j == 5) {
break;
}
cout << "*" << " ";
}
cout << endl;
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
# 6.4.2 continue语句
作用: 在==循环语句==中,跳过本次循环中余下尚未执行的语句,继续执行下一次循环
示例:
int main() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
continue;
}
cout << i << endl;
}
return 0;
}
2
3
4
5
6
7
8
9
注意:continue并没有使整个循环终止,而break会跳出循环
# 6.4.3 goto语句
作用: 可以无条件跳转语句
语法: goto 标记;
解释: 如果标记的名称存在,执行到goto语句时,会跳转到标记的位置
示例:
int main() {
cout << "1" << endl;
goto FLAG;
cout << "2" << endl;
cout << "3" << endl;
cout << "4" << endl;
FLAG:
cout << "5" << endl;
return 0;
}
//1
//5
2
3
4
5
6
7
8
9
10
11
12
注意:在程序中不建议使用goto语句,以免造成程序流程混乱
# 6.4.4 跳转语句底层原理
break 和 continue 的汇编本质:break 翻译为一条无条件跳转指令 jmp,跳转目标是循环结束后的第一条指令;continue 也是 jmp,跳转目标是循环的条件判断处(for循环则跳到步进表达式)。
; for (int i = 0; i < n; i++) {
; if (condition) continue;
; if (condition2) break;
; body;
; }
LOOP_START:
cmp [i], [n]
jge LOOP_END ; 循环条件不满足则退出
; if (condition) continue
test [condition], 1
jnz STEP ; continue → 跳到步进
; if (condition2) break
test [condition2], 1
jnz LOOP_END ; break → 跳到循环后
; body
STEP:
inc [i] ; 步进
jmp LOOP_START
LOOP_END:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
goto 的汇编实现:goto 是最"诚实"的语句——它直接对应一条 jmp 指令,没有任何附加逻辑。这就是为什么 goto 既强大又危险:它可以跳转到函数内的任何位置,完全绕过正常的流程控制。
疑惑:goto 真的那么可怕吗?为什么 Linux 内核大量使用 goto?
答疑:goto 在结构化编程中被批评,是因为滥用 goto 会导致"意大利面条代码"——跳来跳去无法追踪执行流。但 goto 本身只是一个工具。
论证:Linux 内核中 goto 的典型用法是错误处理和资源清理:
int doSomething() {
int* buf1 = new int[100];
if (!buf1) goto fail1;
int* buf2 = new int[200];
if (!buf2) goto fail2;
int* buf3 = new int[300];
if (!buf3) goto fail3;
// ... 正常逻辑 ...
delete[] buf3;
delete[] buf2;
delete[] buf1;
return 0;
fail3: delete[] buf2;
fail2: delete[] buf1;
fail1: return -1;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
结果展示:这种"向后 goto"模式清晰表达了资源释放的逆序关系,避免了深层嵌套的 if-else。当然在现代 C++ 中,我们有 RAII(资源获取即初始化)和智能指针来替代这种模式,但在 C 语言和内核编程中,goto 的错误处理模式仍然是最佳实践。
C++中更好的替代方案:使用 RAII 和析构函数自动释放资源,彻底消除手动清理的需求:
#include <memory>
#include <vector>
int doSomethingModern() {
auto buf1 = std::make_unique<int[]>(100);
auto buf2 = std::make_unique<int[]>(200);
auto buf3 = std::make_unique<int[]>(300);
// 如果任何分配失败,前面的unique_ptr会自动释放
// 函数结束时,所有资源自动释放
return 0;
}
2
3
4
5
6
7
8
9
10
11
# 6.4.5 跳转语句训练题
训练1:实现一个文本搜索程序,在字符串数组中搜索目标字符串,找到后用 break 提前退出:
#include <iostream>
#include <string>
using namespace std;
int main() {
string words[] = {"apple", "banana", "cherry", "date", "elderberry",
"fig", "grape", "honeydew", "kiwi", "lemon"};
int size = sizeof(words) / sizeof(words[0]);
string target = "fig";
int foundIndex = -1;
int comparisons = 0;
for (int i = 0; i < size; i++) {
comparisons++;
if (words[i] == target) {
foundIndex = i;
break; // 找到后立即退出
}
}
if (foundIndex >= 0)
cout << "找到 \"" << target << "\" 在位置 " << foundIndex
<< ",比较了 " << comparisons << " 次" << endl;
else
cout << "未找到,比较了 " << comparisons << " 次" << endl;
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
思考:如果不用 break,而是用标志变量控制循环退出,代码会怎样?哪种方式更清晰?
训练2:使用 continue 实现一个数据清洗程序,跳过无效数据:
#include <iostream>
#include <vector>
using namespace std;
int main() {
// 原始数据中混有无效值(负数和超大值)
vector<int> rawData = {23, -1, 45, 67, -5, 89, 999, 12, -3, 56, 78, 1000, 34};
vector<int> cleanData;
int skipped = 0;
for (int val : rawData) {
// 跳过无效数据
if (val < 0 || val > 100) {
skipped++;
continue;
}
cleanData.push_back(val);
}
cout << "原始数据: " << rawData.size() << " 个" << endl;
cout << "有效数据: " << cleanData.size() << " 个" << endl;
cout << "跳过: " << skipped << " 个" << endl;
double sum = 0;
for (int val : cleanData) sum += val;
cout << "有效数据平均值: " << sum / cleanData.size() << endl;
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
思考:continue 适合在什么场景使用?如果过滤条件很复杂(多个条件),用 continue 还是 if-else 包裹更好?
训练3:不使用 goto,实现一个跳出多层嵌套循环的搜索:
#include <iostream>
using namespace std;
int main() {
// 在二维数组中搜索目标值
int matrix[5][5] = {
{1, 2, 3, 4, 5},
{6, 7, 8, 9, 10},
{11, 12, 13, 14, 15},
{16, 17, 18, 19, 20},
{21, 22, 23, 24, 25}
};
int target = 18;
// 方法1:标志变量
bool found = false;
for (int i = 0; i < 5 && !found; i++) {
for (int j = 0; j < 5 && !found; j++) {
if (matrix[i][j] == target) {
cout << "方法1: 找到 " << target << " 在 [" << i << "][" << j << "]" << endl;
found = true;
}
}
}
// 方法2:封装成函数,用return跳出
// (此处省略函数定义,自行实现)
// 方法3:C++11 lambda + return
[&]() {
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
if (matrix[i][j] == target) {
cout << "方法3: 找到 " << target << " 在 [" << i << "][" << j << "]" << endl;
return; // 从lambda返回,等效于跳出所有循环
}
}
}
}();
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
思考:三种跳出多层循环的方式(标志变量、封装函数、lambda+return)各有什么优缺点?你最喜欢哪种?
# 6.4.6 综合案例与思考
综合案例:跳转语句在实际场景中的应用
#include <iostream>
#include <vector>
using namespace std;
int main() {
// 1. break:在查找到目标后立即退出
cout << "=== break: 查找元素 ===" << endl;
vector<int> data = {15, 23, 7, 42, 8, 56, 31};
int target = 42;
bool found = false;
for (int i = 0; i < data.size(); ++i) {
if (data[i] == target) {
cout << "找到 " << target << " 在位置 " << i << endl;
found = true;
break; // 找到后不需要继续遍历
}
}
if (!found) cout << "未找到" << endl;
// 2. continue:跳过不满足条件的元素
cout << "\n=== continue: 过滤处理 ===" << endl;
cout << "偶数: ";
for (int i = 1; i <= 20; ++i) {
if (i % 2 != 0) continue; // 跳过奇数
cout << i << " ";
}
cout << endl;
// 3. break跳出嵌套循环(只跳一层)
cout << "\n=== break在嵌套循环中 ===" << endl;
for (int i = 0; i < 5; ++i) {
for (int j = 0; j < 5; ++j) {
if (j == 3) break; // 只跳出内层循环
cout << "(" << i << "," << j << ") ";
}
cout << endl;
}
// 4. 用标志变量跳出多层循环(替代goto)
cout << "\n=== 跳出多层循环 ===" << endl;
bool shouldBreak = false;
for (int i = 0; i < 10 && !shouldBreak; ++i) {
for (int j = 0; j < 10 && !shouldBreak; ++j) {
if (i * 10 + j == 25) {
cout << "在i=" << i << ",j=" << j << "处退出" << endl;
shouldBreak = true;
}
}
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
案例知识融合:这个案例展示了跳转语句的实际应用——break在查找到目标后提前退出循环避免不必要的遍历、continue在过滤场景中跳过不满足条件的元素、break在嵌套循环中只跳出一层的特点,以及用标志变量替代goto跳出多层循环的优雅方式。
思考题:
break只能跳出最内层循环,如果需要跳出多层嵌套循环,除了goto和标志变量,还有什么方法?(提示:可以把循环封装成函数)goto在C++中被认为是"不好的实践",但Linux内核代码中大量使用了goto做错误处理。你如何看待这个矛盾?continue和break看似简单,但在复杂循环中使用不当可能导致逻辑混乱。有什么最佳实践可以避免这种问题?
# 6.5 综合案例:猜数字游戏
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
int main() {
srand(static_cast<unsigned>(time(nullptr)));
int secret = rand() % 100 + 1; // 1-100的随机数
int guess, attempts = 0;
const int maxAttempts = 7;
cout << "=== 猜数字游戏 ===" << endl;
cout << "我想了一个1-100之间的数字,你有" << maxAttempts << "次机会猜。" << endl;
do {
cout << "\n第 " << (attempts + 1) << " 次猜测: ";
cin >> guess;
attempts++;
if (guess < 1 || guess > 100) {
cout << "请输入1-100之间的数字!" << endl;
continue; // 无效输入不计入次数? 这里依然计入了
}
if (guess == secret) {
cout << "恭喜你猜对了!用了 " << attempts << " 次。" << endl;
// 评价
switch (attempts) {
case 1: cout << "评价:天才!一次就中!" << endl; break;
case 2: case 3: cout << "评价:优秀!" << endl; break;
case 4: case 5: cout << "评价:不错!" << endl; break;
default: cout << "评价:再接再厉!" << endl;
}
break;
} else if (guess < secret) {
cout << "太小了!";
} else {
cout << "太大了!";
}
cout << "还剩 " << (maxAttempts - attempts) << " 次机会。" << endl;
} while (attempts < maxAttempts);
if (guess != secret) {
cout << "\n很遗憾,次数用尽!答案是 " << secret << endl;
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
案例知识融合:这个猜数字游戏综合运用了本章所有知识点——do-while循环确保至少执行一次、if-else if-else多条件判断大小关系、switch评价猜测次数、break在猜中后跳出循环、continue跳过无效输入、const定义最大尝试次数。是一个完整的流程控制综合实战。
# 6.6 思考题
编译器优化与流程控制:现代编译器在
-O2优化级别下,会对 if-else 做分支预测提示优化。GCC 提供了__builtin_expect宏,Linux 内核封装为likely()/unlikely()宏。你了解分支预测对性能的影响吗?在什么场景下值得手动添加分支预测提示?结构化编程的边界:Dijkstra 1968 年发表了著名的"Go To Statement Considered Harmful"论文,推动了结构化编程。但 C++ 至今保留了
goto。结合你对 RAII、异常处理和goto的理解,你认为在现代 C++ 中还有使用goto的合理场景吗?循环 vs 递归:任何循环都可以改写为递归,反之亦然。在 C++ 中,循环通常比递归性能更好(因为函数调用有栈帧开销)。但函数式编程语言偏好递归。你认为在 C++ 中什么时候应该用递归而不是循环?(提示:考虑树遍历、分治算法等场景)
C++20 的协程(Coroutine):C++20 引入了协程,它提供了一种新的流程控制方式——可以"暂停"和"恢复"函数执行。这打破了传统的"函数要么完整执行要么不执行"的模型。你了解协程能解决什么问题吗?它和传统的循环/条件分支有什么根本区别?
# 6.7 卷一改造增补:现代 C++ 流程控制三件套
本节为卷一新增。这三个特性看似小,却几乎出现在现代 C++ 项目的每一个函数里,是新手向「现代风格」过渡的分水岭。
# 6.7.1 if / switch 带初始化语句(C++17)
目的:把变量的作用域严格限制在条件块内,杜绝「悬挂变量」污染外层作用域。
// C++17 之前
auto it = m.find(key);
if (it != m.end()) { use(it->second); }
// it 在这里依然可见,容易被误用
// C++17:把变量塞进 if 的「初始化部分」
if (auto it = m.find(key); it != m.end()) {
use(it->second);
}
// 出了 } 之后 it 就不存在了
2
3
4
5
6
7
8
9
10
switch 同样支持:
switch (auto code = parse(); code) {
case 0: /* ... */ break;
case -1: /* ... */ break;
}
2
3
4
# 6.7.2 if constexpr(C++17):编译期分支
目的:在模板里根据类型属性走不同分支,不匹配的分支根本不会被编译——彻底替代传统的 SFINAE 与 tag dispatch 黑魔法。
template <typename T>
auto serialize(const T& v) {
if constexpr (std::is_arithmetic_v<T>) {
return std::to_string(v); // 数值类型走这里
} else if constexpr (std::is_same_v<T, std::string>) {
return v; // string 直接返回
} else {
return v.to_json(); // 自定义类型调用成员函数
}
}
2
3
4
5
6
7
8
9
10
普通 if 在模板里会要求所有分支都能编译,而 if constexpr 只编译被选中的分支——这就是它能调用 v.to_json() 而不出错的关键。
# 6.7.3 结构化绑定(Structured Bindings, C++17)
目的:一次性把聚合类型(pair/tuple/struct/数组)拆开成多个命名变量。
// 遍历 map:C++17 之前
for (auto it = m.begin(); it != m.end(); ++it) {
cout << it->first << "=" << it->second;
}
// C++17:直接拆开
for (const auto& [key, value] : m) {
cout << key << "=" << value;
}
// 接收多返回值
struct Result { int code; std::string msg; };
Result query();
auto [code, msg] = query();
// 配合 if-init 简直是绝配
if (auto [it, ok] = m.insert({k, v}); ok) {
cout << "插入成功";
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
注意三处坑:
- 默认是值拷贝,要修改原值用
auto& [k, v],要避免拷贝用const auto& [k, v]。 - 绑定的名字不是引用,但底层会保持引用语义(编译器优化得很好)。
- C++17 还不能给绑定的名字加
[[maybe_unused]];C++26 起允许auto& [_, v] = ...,C++17/20 中可用[[maybe_unused]] auto&。
# 6.7.4 推荐阅读
# 6.8 新手陷阱 Top 5
| # | 陷阱 | 说明 |
|---|---|---|
| 1 | if (a = b) 写错赋值 | 应为 if (a == b),开 -Wparentheses 警告 |
| 2 | switch 漏 break 串档 | 故意 fallthrough 用 [[fallthrough]]; 标注(C++17) |
| 3 | 浮点数循环步长 | for (float f=0; f<1; f+=0.1) 永远到不了 1.0 |
| 4 | 范围 for 修改容器 | 循环中 push_back 导致迭代器失效 |
| 5 | goto 跨过对象构造 | 编译报错;即使能编译也会破坏 RAII |