第四章 流程控制结构
一、项目引入-猜数字游戏
1.1 先看成品
先看写完之后长什么样。答案先固定成 42,随机数留到 3.6 节。
#include <iostream> using namespace std; int main() { int answer = 42; int guess = 0; int count = 0; while (guess != answer) { cout << "猜一个 1 到 100 的数: "; cin >> guess; count++; if (guess > answer) { cout << "猜大了" << endl; } else if (guess < answer) { cout << "猜小了" << endl; } else { cout << "猜中了,一共猜了 " << count << " 次" << endl; } } return 0; }跑起来是这个节奏。
猜一个 1 到 100 的数: 50 猜大了 猜一个 1 到 100 的数: 25 猜小了 猜一个 1 到 100 的数: 37 猜小了 猜一个 1 到 100 的数: 44 猜大了 猜一个 1 到 100 的数: 40 猜小了 猜一个 1 到 100 的数: 42 猜中了,一共猜了 6 次这个猜法有章法,先砍一半,再砍一半,六次收工。1 到 100 的范围,二分查找最多七次,这是算得出来的,二的七次方是一百二十八,装得下一百个可能。程序该干的事它都干了,读输入、比大小、报结果,猜中了自动停。注意两个细节,循环不知道要跑多少轮,猜中才停;每轮说的话不一样,看猜的数是大是小。这两件事,一个靠循环,一个靠选择。
┌──────────────────────────────────────────────────────────┐ │ 猜数字游戏走这几步 │ ├──────────────────────────────────────────────────────────┤ │ 先想数,再循环猜,猜中了才结算 │ │ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ 开局 埋随机种子,生成 1 到 100 的一个数 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ │ v │ │ ┌────────────────────────────────────────────────────┐ │ │ │ 进循环 提示玩家输入,读进一个整数 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ │ v │ │ ┌────────────────────────────────────────────────────┐ │ │ │ 比大小 猜大了、猜小了、猜中了,三条路 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ │ v │ │ ┌────────────────────────────────────────────────────┐ │ │ │ 跳出结算 报告猜了几次,程序收工 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ 猜大猜小都不退循环,接着提示,直到猜中为止 │ │ 次数靠一个计数器,每猜一次加一 │ └──────────────────────────────────────────────────────────┘1.2 拆开看,就三样东西
把这段程序按行拆开,用到的知识摊开就这几样。
| 段落 | 代码里的样子 | 属于哪一块 |
|---|---|---|
| 准备 | answer guess count 三个变量 | 第二章的老本 |
| 反复猜 | while 加一个条件 | 循环结构 |
| 分岔提示 | if else if 三分支 | 选择结构 |
| 猜中收工 | 条件变假,循环自己退 | 循环的出口 |
前两章的东西这一章全用得上。cin 读数,第二章学的变量累加,第三章学的输出格式化,都是现成的。真正新加的只有两样,选择结构和循环结构。这一章的正文就讲这两块,最后合起来,就是开头那个游戏。
选择结构回答的是该走哪条路。猜大了走大的提示,猜小了走小的提示,猜中了走结算。循环结构回答的是这件事要做几遍。读数、比较、提示,这一套要重复到猜中为止,写几遍都不对,要写成一个会自己转的圈。
1.3 没有流程控制之前
回头看表哥那十来个 if。他把程序写成了清单,猜 50 做什么,猜 51 做什么,一条条列。清单的问题在于长度固定,而游戏的长度不固定。有人五次猜中,有人六十次猜中,清单写不到那么长。
流程控制和清单的差别在这一点上。清单是照着走,走完拉倒;流程控制是会看情况,情况不变就接着转。while 那一行,条件是真的就再转一圈,是假的就出门,转几圈由玩游戏的人决定,不由写程序的人决定。
猜数字是个好教具。它够小,四十行,一下能看完;它也够全,选择、循环、跳转、输入输出全用上了。这一章把它拆开讲,下一章再把它拼回去。
二、选择结构
2.1 条件的积木
if 后面挂的是一个条件,条件由关系运算符和逻辑运算符搭出来。
关系运算符六个,都是老熟人。
| 运算符 | 念法 | 例子 | 结果 |
|---|---|---|---|
| < | 小于 | 3 < 5 | 真 |
| > | 大于 | 3 > 5 | 假 |
| <= | 小于等于 | 5 <= 5 | 真 |
| >= | 大于等于 | 5 >= 6 | 假 |
| == | 等于 | 5 == 5 | 真 |
| != | 不等于 | 5 != 5 | 假 |
等于号是两个等号。一个等号是赋值,把右边的值塞进左边的变量,第二章说过。写成 if (x = 0) 是先把 x 改成 0,再拿 0 当条件,永远是假,这是新手头号事故,编译器多半还会补一个警告。
逻辑运算符三个,负责把多个条件拼起来。
| 运算符 | 含义 | 例子 |
|---|---|---|
| && | 并且,两边都真才真 | 年龄够并且有余票 |
| || | 或者,一边真就真 | 是会员或者是学生 |
| ! | 取反,真变假 | 不是空 |
判断一个数在 1 到 100 之间,要写两个条件用 && 连。
if (x >= 1 && x <= 100) { cout << "在范围内" << endl; }数学里 1 <= x <= 100 这么写很顺,C++ 里不行。运算从左往右,先算 1 <= x 得 0 或 1,再拿这个 0 或 1 去和 100 比,结果永远是 1,条件恒真。这个坑第三章踩过一次,混合比较翻车,同一个道理。
优先级上,关系运算符比逻辑运算符高,&& 比 || 高。拿不准就加括号,括号不要钱,还能让读代码的人少费脑子。
2.2 if 的三种形态
一个 if 有三种长法,分支一个比一个多。
┌────────────────────────────────────────────────────────┐ │ if 的三种形态 │ ├────────────────────────────────────────────────────────┤ │ 一个 if,三种长法,分支越来越多 │ │ │ │ +--------------+------------------+----------------+ │ │ |形态 |长相 |什么时候用 | │ │ +--------------+------------------+----------------+ │ │ |单分支 |if 加花括号 |条件成立才做 | │ │ |双分支 |if 配 else |二选一 | │ │ |多分支 |if else if 链 |三个以上候选 | │ │ +--------------+------------------+----------------+ │ │ │ │ if 后面挂条件,成立进花括号,不成立跳过去 │ │ else 贴着上一个 if,中间别插别的语句 │ └────────────────────────────────────────────────────────┘单分支,条件成立才做,不成立就跳过去。
if (score >= 60) { cout << "及格了" << endl; }双分支,二选一,总有一边要执行。
if (num % 2 == 0) { cout << "偶数" << endl; } else { cout << "奇数" << endl; }多分支,一串 if else if 挂下来,从第一个条件开始试,哪个中了走哪条。
if (score >= 90) { cout << "优" << endl; } else if (score >= 80) { cout << "良" << endl; } else if (score >= 60) { cout << "中" << endl; } else { cout << "差" << endl; }这段代码有个顺序讲究。判断从大到小排,先问是不是优,不是再问良。写成从小到大就全乱,60 分先被中接走,90 分永远摸不到优的门。多分支的条件,谁严谁先。
else 配对是另一个事故点。else 找的是离它最近的、还没配对的 if。缩进好看不等于逻辑好看,下面这段看着是两层,其实是一层。
if (x > 0) if (y > 0) cout << "第一象限" << endl; else cout << "x 不是正数" << endl;编译器眼里,那个 else 属于内层 if。x 是负数的时候,内层 if 压根没执行,else 也没机会执行,屏幕干干净净。解决办法是花括号一律写上,哪怕只有一句话。这不是风格问题,是保命。
if (x > 0) { if (y > 0) { cout << "第一象限" << endl; } } else { cout << "x 不是正数" << endl; }嵌套 if 的真实用例是二维判断。先分性别,再分年龄段,一层套一层,每层管一个维度。层数最多两层,三层以上就该换成 switch 或者查表,人读不动。
2.3 条件运算符
有一个场景很常见,两个值里挑大的。用 if else 写,先声明再摆一个判断,啰嗦。
int max; if (a > b) { max = a; } else { max = b; }条件运算符一行搞定,它是唯一的三目运算符,三个位置各就各位。
int max = (a > b) ? a : b;问号前面是条件,真走问号后面,假走冒号后面。括号建议一直带着,它的优先级低得反常,混进复杂表达式里容易和赋值号搅在一起。这句话也说给自己,Debug 打多了就知道,一行能装下的聪明,常常是三天后看不懂的伏笔。
2.4 switch 与穿透
分支超过三个,if else if 链就开始显长。switch 是为这种场合生的,拿着一个值去对一堆标签。
int level = 2; switch (level) { case 1: cout << "初级" << endl; break; case 2: cout << "中级" << endl; break; case 3: cout << "高级" << endl; break; default: cout << "等级不明" << endl; }执行的时候先看 switch 后面挂的值,这里是 2。从上往下找标签,找到 case 2,从那一行开始执行,打印中级,碰上 break,跳出 switch。default 是兜底,没有一个 case 对上的时候归它,写不写都行,建议写。
┌──────────────────────────────────────────────────────────┐ │ switch 是怎么跑的 │ ├──────────────────────────────────────────────────────────┤ │ switch 拿着值去对标签,对上了从那儿开始跑 │ │ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ 对标签 值等于哪个 case 的常量,就从哪行起跑 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ │ v │ │ ┌────────────────────────────────────────────────────┐ │ │ │ 往下顺 没有 break 就一条条掉下去,这叫穿透 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ │ v │ │ ┌────────────────────────────────────────────────────┐ │ │ │ break 当场跳出 switch,后面的 case 不看 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ 每个分支末尾都该有 break,忘了就是穿透事故 │ │ case 只能对整数,范围题交给 if else if │ └──────────────────────────────────────────────────────────┘break 是这套机制里最容易忘的东西。忘了会怎样,看一眼穿透事故。
int level = 1; switch (level) { case 1: cout << "初级" << endl; case 2: cout << "中级" << endl; case 3: cout << "高级" << endl; default: cout << "等级不明" << endl; }输入 1,屏幕上四行全出来了。case 1 执行完没有东西拦着,顺着掉进 case 2,再掉进 case 3,最后连 default 一起执行。这个行为叫穿透,标准规定如此,不是编译器抽风。有些场合故意用它,几个 case 共享一段代码,日常多数是忘写。判断标准很简单,写的时候有没有想过要穿透,没想过就是忘了。
switch 的规矩还有几条,都是硬性的。
| 规矩 | 说明 |
|---|---|
| 只能对整数 | int char 枚举都行,字符串不行 |
| 标签是常量 | case 后面跟编译期定得下的数,不能是变量 |
| 标签不重复 | 两个 case 1,编译直接拦 |
| default 随便放 | 放头放尾都行,习惯放最后 |
分支多且值是整数,用 switch;条件带范围,比如判断优良好中差,用 if else if。switch 对范围没有天赋,case 后面写 >= 90 直接编不过。
三、循环结构
3.1 while 先判后跑
while 的规矩一句话,条件是真的,身体就再跑一遍,每跑完一遍重新问一次。
int i = 1; while (i <= 5) { cout << i << endl; i++; }屏幕上出来 1 到 5。注意 i++ 那一行,它让条件有变化。去掉它,i 永远是 1,条件永远为真,程序永远跑下去,这就是死循环。
死循环的样子很吓人,屏幕刷到看不见光标, Ctrl+C 按下去才停。第一次见容易慌,慌可以,别直接关窗口,死循环是本章必修课,见一次修一次就长记性。
最常见的死循环病根有两个。一个是条件里写赋值,while (flag = 1) 每次进来先把 flag 设成 1,再拿 1 当条件,恒真。编译器会给警告,大意是建议加括号确认是不是赋值,看见这个警告就当错误处理。另一个是忘了更新,计数器不走了,条件不动了,圈就不停了。
int flag = 1; while (flag = 1) { // 赋值,不是比较,恒真 cout << "停不下来" << endl; }while 有个兄弟叫 do while,俩人的差别在先后。
┌──────────────────────────────────────────────────────┐ │ while 和 do while 差在哪 │ ├──────────────────────────────────────────────────────┤ │ 两种循环,判条件和跑身体的先后反过来 │ │ │ │ +--------------+----------------+----------------+ │ │ |循环 |先干什么 |身体最少跑几次 | │ │ +--------------+----------------+----------------+ │ │ |while |先判条件 |可能一次都不跑 | │ │ |do while |先跑身体 |至少跑一次 | │ │ +--------------+----------------+----------------+ │ │ │ │ 循环进门先后不同,空转的表现就不同 │ │ do while 的分号是语法,漏了报错位置发懵 │ └──────────────────────────────────────────────────────┘3.2 do while 先跑后判
do while 把身体提到前头,条件挪到后头。翻译成人话,先跑一遍,再问还要不要跑。
int choice; do { cout << "1 开始 2 设置 0 退出" << endl; cout << "选一个: "; cin >> choice; } while (choice != 0);菜单用 do while 正合适。菜单至少得显示一次,while 先问后跑,条件凑不巧就一次不显示,do while 没这个问题。输 0 退出,输别的继续显示,逻辑顺。
两条使用规矩。分号不能省,while (choice != 0); 这个分号是语法的一部分,漏了编译报错,报的位置还容易看懵。花括号里的身体和 while 是绑定的,break 一样管用。
三种循环的分工,一句话版本。次数知道用 for,次数看情况用 while,至少跑一次用 do while。
| 循环 | 判条件时机 | 身体最少几次 | 典型场合 |
|---|---|---|---|
| while | 进圈之前 | 可能零次 | 猜数字、读文件到结尾 |
| do while | 跑完之后 | 至少一次 | 菜单、重试询问 |
| for | 每轮之前 | 可能零次 | 计数、遍历、九九乘法表 |
3.3 for 为计数而生
while 什么圈都能转,计数的圈转起来啰嗦。定起点,判终点,往前迈一步,三件事摊在 while 里是这个样子。
int i = 1; while (i <= 100) { cout << i << endl; i++; }for 把这三件事收进一行,一格放一样。
for (int i = 1; i <= 100; i++) { cout << i << endl; }┌────────────────────────────────────────────────────────┐ │ for 的三段各管什么 │ ├────────────────────────────────────────────────────────┤ │ for 的三个格子各管一段,分号是格子之间的墙 │ │ │ │ +------------+----------------------+--------------+ │ │ |格子 |管什么 |跑几次 | │ │ +------------+----------------------+--------------+ │ │ |初始化 |定计数器的起点 |只跑一次 | │ │ |条件 |决定还跑不跑 |每轮都跑 | │ │ |更新 |计数器往前走一步 |每轮都跑 | │ │ +------------+----------------------+--------------+ │ │ │ │ 三段都能省,分号不能省,省光就是死循环 │ │ 计数器习惯用 i,嵌套里层用 j,别重用 │ └────────────────────────────────────────────────────────┘执行顺序得盯一眼。初始化只来一次,条件每轮都问,更新每轮都跑,三个格子的节奏不一样。
┌──────────────────────────────────────────────────────────┐ │ 一轮 for 按什么顺序走 │ ├──────────────────────────────────────────────────────────┤ │ 一轮循环里,条件和更新隔着身体打招呼 │ │ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ 初始化 进屋前做一次,定好起点 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ │ v │ │ ┌────────────────────────────────────────────────────┐ │ │ │ 判条件 真的往下走,假的出门 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ │ v │ │ ┌────────────────────────────────────────────────────┐ │ │ │ 跑身体 该做的事写在这儿 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ │ v │ │ ┌────────────────────────────────────────────────────┐ │ │ │ 更新 计数器加一,再回去判条件 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ 顺序是先判后跑再更新,更新完回到判断 │ │ 多跑一轮,多半是判断写成了小于等于 │ └──────────────────────────────────────────────────────────┘初学者最常栽在边界上。打印 1 到 10,条件写成 i <= 10 对,写成 i < 10 少一个,写成 i <= 11 多一个。拿不准的时候小刀割一下,先跑个 1 到 3,数一数,再放回去跑 1 到 100。多跑一轮少跑一轮的毛病,十个循环 bug 里占三个。
for 的三段都能省,分号不能省。省一段常见于计数器在别处初始化,省两段也有人写,三段全省就是 for (;;),和 while (true) 一个意思,死循环的正式写法。
for (int i = 0, j = 10; i < j; i++, j--) { cout << i << " " << j << endl; }这一段用了逗号表达式,初始化和更新各干两件事,两头往中间走,碰头为止。算是个花活,知道有就行,日常代码里见到的机会不多。
还有一条常考细节。for 里声明的变量,活动范围就在这个 for 里,出了圈就没了。C++ 一向是这样,C 语言要到 C99 才跟上,所以老代码里常见先声明再用的写法,两种都看得懂就行。
3.4 break 和 continue
圈里想提前撤,用 break。圈里想跳过这一轮,用 continue。两个都是本章的关键词,去的地方不一样。
┌────────────────────────────────────────────────────────┐ │ break 和 continue 的去向 │ ├────────────────────────────────────────────────────────┤ │ 两个跳语句,跳的高度不一样 │ │ │ │ +--------------+--------------------+--------------+ │ │ |语句 |跳去哪 |循环还跑吗 | │ │ +--------------+--------------------+--------------+ │ │ |break |直接出循环 |不跑了 | │ │ |continue |回条件判断 |接着跑下一轮 | │ │ +--------------+--------------------+--------------+ │ │ │ │ break 在 switch 里也认,跳出这一层 switch │ │ 嵌了两层循环,break 只跳最近的那一层 │ └────────────────────────────────────────────────────────┘看代码。1 到 10 挨个打,碰上 5 跳过,碰上 8 收工。
for (int i = 1; i <= 10; i++) { if (i == 5) { continue; } if (i == 8) { break; } cout << i << endl; }屏幕上挨个出来 1 2 3 4 6 7,六个数字。continue 跳的是本轮剩下的语句,跳完回到更新那一格,计数器照走;break 是整个圈不要了,更新都不回去。
break 只跳最近的一层,这个限定词别丢。想从里层一下撤到最外面,break 不帮忙,得靠一个标记变量加条件判断,那是后话,函数和返回值会给出更干净的解法。
3.5 嵌套循环与九九乘法表
圈里套圈,就是嵌套循环。外层走一步,内层跑一整轮,这个节奏是理解嵌套的唯一钥匙。
┌──────────────────────────────────────────┐ │ 嵌套循环谁先跑 │ ├──────────────────────────────────────────┤ │ 外层走一步,内层要跑完一整轮 │ │ │ │ 外层 i 从 1 到 9,每走一步 │ │ │ │ │ v │ │ 内层 j 从 1 到 i,整整齐齐跑完 │ │ ├─ i 是 1 的时候,j 只跑 1 轮 │ │ ├─ i 是 9 的时候,j 跑 9 轮 │ │ └─ 总共 45 个等式,一行比一行长 │ │ │ │ 内层跑完,外层才更新,不是同时往前跑 │ │ 看不清的时候,两个计数器都打印出来看 │ └──────────────────────────────────────────┘九九乘法表是嵌套循环的保留节目。九行,第一行一个等式,第九行九个,一共四十五个。用循环写,外层管行,内层管列。
for (int i = 1; i <= 9; i++) { for (int j = 1; j <= i; j++) { cout << j << "x" << i << "=" << i * j << "\t"; } cout << endl; }内层的条件是 j <= i,不是 j <= 9。就这一个小于等于,决定了第一行只有一个等式,第九行有九个。改成 j <= 9,出来的是方阵,一行不落,三角形摊成正方形,能跑,但不是要的那个。
外层循环体里就两件事,内层循环加一个换行。内层负责把一行填满,外层负责换下一行。套第三层不是不行,是很少有人读得动,两层是人的舒适区上限。
嵌套循环最容易出的 bug 是计数器写重。两层都用 i,内层一轮把外层的 i 搅得天翻地覆,圈数全乱。规矩是外层 i 内层 j,第三层才轮到 k,别省这点字母。
3.6 随机数与完整版
固定答案 42 的游戏玩第二轮就没意思了,答案得会变。变之前先知道,计算机里的随机是假随机,由数学公式一批批算出来,起点相同算出来的一串数完全相同。
rand 函数从<cstdlib>来,每次调用给一个数,范围 0 到 RAND_MAX。RAND_MAX 是个常量,标准只要求不小于 32767,Windows 上常见就是 32767,Linux 和 macOS 上常是二十多亿。不播种,每次运行 rand 吐的数一个样,程序昨天今天一个答案。
srand 负责播种,配 time 拿当前时刻当种子,每次跑都不一样。
#include <iostream> #include <cstdlib> #include <ctime> using namespace std; int main() { srand((unsigned int)time(nullptr)); for (int i = 0; i < 5; i++) { cout << rand() << endl; } return 0; }跑两遍,数字不一样了。要 1 到 100,取余再平移,rand % 100 + 1,取余得 0 到 99,加 1 得 1 到 100。
C++11 起有了新玩具,<random>头文件,随机数引擎加分布,两行搞定,分布更均匀,写法也更直白。
#include <iostream> #include <random> using namespace std; int main() { random_device rd; mt19937 rng(rd()); uniform_int_distribution<int> dist(1, 100); for (int i = 0; i < 5; i++) { cout << dist(rng) << endl; } return 0; }初学者建议先认 rand 那一套,老代码里到处都是,读得懂才有然后。<random>是写新代码的选择,等哪天要均匀分布、要正态分布、要可复现的随机序列,再回来细聊。
| 做法 | 头文件 | 一句话点评 |
|---|---|---|
| rand 加 srand | <cstdlib>加<ctime> | 老搭档,教程和遗产代码都靠它 |
| mt19937 加分布 | <random> | 现代做法,均匀可控,该早点换 |
现在拼完整版。五摊东西,埋种子、生成答案、循环猜、计数器、结算。
┌────────────────────────────────────────────────────────┐ │ 猜数字程序分五摊 │ ├────────────────────────────────────────────────────────┤ │ 完整版还只有一个 main,里面分了五摊 │ │ │ │ +------------+----------------------+--------------+ │ │ |段落 |干什么 |用到什么 | │ │ +------------+----------------------+--------------+ │ │ |埋种子 |让每次运行答案不同 |srand 加 time | │ │ |生成答案 |定一个 1 到 100 的数 |rand 加取余 | │ │ |循环猜 |读、比、提示,猜中才停|while 加 if | │ │ |计数器 |记猜了几次 |一个 int | │ │ |结算 |跳出后报告成绩 |break 跳这儿 | │ │ +------------+----------------------+--------------+ │ │ │ │ 五摊按顺序排,循环那块是本章的组合拳 │ │ 拆函数留到下一章,现在先堆在 main 里 │ └────────────────────────────────────────────────────────┘#include <iostream> #include <cstdlib> #include <ctime> using namespace std; int main() { srand((unsigned int)time(nullptr)); int answer = rand() % 100 + 1; int guess = 0; int count = 0; while (guess != answer) { cout << "猜一个 1 到 100 的数: "; cin >> guess; count++; if (guess > answer) { cout << "猜大了" << endl; } else if (guess < answer) { cout << "猜小了" << endl; } else { cout << "猜中了,一共猜了 " << count << " 次" << endl; } } return 0; }和 1.1 节的差别只有两行,一个 srand,一个 rand % 100 + 1。流程一行没动,这就是流程控制值钱的地方,壳子搭好,换内容只是换两行。
顺手指出一个脆弱处。cin 里喂个字母,第三章讲过,failbit 一置,cin 直接摆烂,guess 再也不更新,循环停不下来。交差之前先用字母试一次,崩了就知道要补第三章那套三板斧。
四、小结
┌──────────────────────────────────────────────────────────┐ │ 本章收口 │ ├──────────────────────────────────────────────────────────┤ │ 一章学完,手上就该有这几样东西 │ │ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ 选择 if 三形态,switch 加 break │ │ │ └────────────────────────────────────────────────────┘ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ 循环 while、do while、for 各就各位 │ │ │ └────────────────────────────────────────────────────┘ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ 跳转 break 跳出,continue 跳过本次 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ 猜数字一个游戏,把选择、循环、跳转全用上了 │ │ 读代码先找循环,再看循环里的分支 │ │ 下一章把 main 里这五摊收进函数 │ └──────────────────────────────────────────────────────────┘| 知识 | 一句话 | 最容易栽在哪 |
|---|---|---|
| if 三形态 | 单双多,分支一个个加 | 条件顺序写反 |
| else 配对 | 找最近的没配对 if | 缩进骗人,花括号救命 |
| switch | 整数对标签,break 跳出 | 忘 break 全穿透 |
| while | 先判后跑 | 条件恒真,忘了更新 |
| do while | 先跑后判,至少一次 | 漏分号 |
| for | 三段一格,计数专用 | 边界小于等于 |
| break | 出圈 | 嵌套里只跳一层 |
| continue | 跳过本轮 | 更新不走就死循环 |
| 嵌套循环 | 外层一步内层一轮 | 计数器重用 |
| 随机数 | 先播种再 rand | 不播种答案不变 |
五句话带走。流程控制不是新语法,是把程序的走法从直线变成会看路。选择看条件,循环看真假,跳转让走法更利索。if 的花括号一律写上,这是本章性价比最高的一条。猜数字一个游戏把全章的关键字用全了,读它三遍胜过读三十个片段。写死循环别慌, Ctrl+C 加检查条件,两步搞定。
课后练习四道,由易到难。改猜数字,加次数上限,七次没中就报答案是几,重开一轮。九九乘法表倒过来打,第九行九个等式第一行一个。打印 100 以内的素数,用取余判一遍,break 提前收工。写个菜单,用 do while 包着,选 0 才退出,选错给提示。
附录|流程控制速查卡
| 类别 | 关键词 | 一句话提醒 |
|---|---|---|
| 单分支 | if 加花括号 | 条件真才进 |
| 双分支 | if 配 else | 二选一必执行 |
| 多分支 | if else if 链 | 严条件放前面 |
| 三目 | 条件 ? 甲 : 乙 | 一行二选一,加括号 |
| 多路开关 | switch case default | 标签是整数常量,break 别忘 |
| 先判循环 | while | 条件假一次不进 |
| 先跑循环 | do while | 至少一次,分号别省 |
| 计数循环 | for | 三段一格,分号不能省 |
| 跳出 | break | switch 里也认,只跳一层 |
| 跳过 | continue | 回条件判断,不走完本轮 |
| 常见现象 | 病根 | 药方 |
|---|---|---|
| 程序停不下来 | 条件恒真或没更新 | 查赋值号,查计数器 |
| 屏幕刷到看不清 | 死循环在打印 | Ctrl+C,先停再查 |
| 多跑或少跑一轮 | 边界写成小于等于 | 先跑三轮数一数 |
| switch 分支全执行 | 忘了 break | 每个 case 补上 |
| else 对不上号 | 缩进骗人 | 花括号写上 |
| 内层循环不跑 | 内层条件一开始就假 | 两个计数器都打印 |
| 每次运行答案一样 | 忘了 srand | 开头埋种子 |
| 喂字母程序卡死 | failbit 没清 | clear 加 ignore 重问 |