文章目录
- [2.3 多分支结构](#2.3 多分支结构)
2.3 多分支结构
单分支和双分支是最简单,也是最常见的分支结构。对于更加复杂的情况,我们可以用这两种分支语句将问题逐步分类再求解,称之为多分支结构。
【P2200 [ABC104A] Rated for Me】编程竞赛网站 AtCode 定期举行编程竞赛。AtCode 的第一场比赛称为 ABC,该比赛的参赛者的评分必须低于 1200。第二场比赛称为 ARC,该比赛的参赛者的评分必须高于或等于 1200 且低于 2800。第三场比赛称为 AGC,该比赛的参赛者的评分必须高于或等于 2800。AtCode 对高桥的评分是 r r r。他应该参加哪一场比赛?
这里有 3 种情况需要检测,可以先使用双分支语句将其分为两类,比如低于 1200 的为一类,其它情况为一类。再用一个双分支语句将其它情况分为低于 2800 和不低于 2800 两类,这样一来便得到了所有的情况。显然第二个分支语句属于第一个分支语句的子类别,于是在代码结构上需要将两个分支语句嵌套起来,关键代码如下。
cpp
if (r < 1200) {
cout << "ABC\n";
}
else { // r >= 1200 的情况
if (r < 2800) { // r >= 1200 但是 r < 2800 的情况
cout << "ARC\n";
}
else { // r >= 2800 的情况
cout << "AGC\n";
}
}
嵌套分支结构在需要分类讨论的问题中会体现出非常清晰明确的分类逻辑,但是代码结构看起来比较松散,较多的缩进在这里起到了反作用,降低了代码的可读性。这是因为程序中两个分支语句分出的 3 个类别事实上是同一层级的,但是代码并没有在同一个缩进层级,直观上会认为 3 个类别并不是同一个层级。如果能把两个分支语句放在同一个缩进层级上,代码可读性将大幅提升,级联 if 语句正是可以满足我们需求的语句。
【小贴士】嵌套 if 语句适用于递进关系,级联 if 语句适用于并列关系。
cpp
if (r < 1200) {
cout << "ABC\n";
}
else if (r < 2800) {
cout << "ARC\n";
}
else {
cout << "AGC\n";
}
事实上,不论是嵌套分支结构还是级联 if 语句,都没有引入新的语法规则,它们遵循的仍然是 if 语句和 if-else 语句的规则。C++ 编译器把 if (条件表达式) 语句 和 if (条件表达式) 语句 else 语句 均当做一条完整的语句来解析,其中语句可以是复合语句。那么对于第一个 else 子句来说,第二个分支语句是否被花括号包裹起来都会被看做一条语句。换言之,级联 if 语句的本质是去掉了花括号并对齐缩进层级的嵌套分支。
【小贴士】建议在写嵌套分支语句时一定使用复合语句,本书中所有的分支结构都使用了复合语句,这是一种良好的代码风格,可以有效避免由于缩进层级带来的悬空 else 问题。
悬空 else,是指缩进层级与实际逻辑不匹配的情况,因为 else 子句与 if 子句的匹配遵循就近原则,即 else 子句会与它前面第一个尚未匹配的可见 if 子句进行匹配。例如下面的代码,else 子句会与第二个 if 子句匹配,但是从缩进层级上来看,本意是期望 else 与第一个 if 子句匹配。
cpp
if (a == 0)
if (b == 0)
cout << "ERROR\n";
else
cout << a / b << endl;
如果把第二个 if 子句用花括号包裹起来,那么它将以复合语句的形式作为第一个 if 子句的一部分,此时它对于 else 子句将是不可见的,根据就近原则,else 子句只能与第一个 if 子句匹配。
【D1238 [23年3月一级] 成绩等级转换】小明想将自己的百分制成绩转换为等级制,请你帮他完成这样的转换。转换规则为 A: 90 ∼ 100 90\sim 100 90∼100;B: 77 ∼ 89 77\sim 89 77∼89;C: 67 ∼ 76 67\sim 76 67∼76;D: 60 ∼ 66 60\sim 66 60∼66;E: 0 ∼ 59 0\sim 59 0∼59。
此题需要分 5 个类别,而且都处于同一个层级(并列关系),可以使用级联 if 语句来实现,保持较高的代码可读性。与上一个问题类似,把 else 子句的功能利用起来,可以让我们的代码中少写许多条件,关键代码如下。
cpp
if (a >= 90) {
cout << "A\n";
}
else if (a >= 77) {
cout << "B\n";
}
else if (a >= 67) {
cout << "C\n";
}
else if (a >= 60) {
cout << "D\n";
}
else {
cout << "E\n";
}
【P1796 [CF675A] Infinite Sequence】给出三个整数 a a a、 b b b、 c c c,求 b b b 是否在首项为 a a a,公差为 c c c 的等差数列中。注意,这个等差数列是无限长的, − 10 9 ≤ a , b , c ≤ 10 9 -10^9≤a,b,c≤10^9 −109≤a,b,c≤109。
首先可以确定公差 c c c 是否为 0,在 c c c 等于 0 的情况下,检测 b b b 与 a a a 是否相等即可。
当 c c c 不等于 0 时,需要进一步检测 b b b 是否在等差数列的增长方向上,即确定 b − a b-a b−a 与 4c$ 是否具有相同的正负号。可以通过检测表达式 (b - a) * c >= 0 是否成立来确定,其中等于 0 表示 b b b 与 a a a 相等的情况。
当 b b b 在等差数列的增长方向上时,需要进一步检测 b b b 是否可以通过首项 a a a 不断地累加公差 c c c 得到,即确定 b − a b-a b−a 是否为 c c c 的倍数。
显然这里的 3 次分类讨论并不在同一层级上(递进关系),所以嵌套分支是最为清晰的代码形式。
cpp
if (c == 0) { // 公差为0
if (b == a) {
cout << "YES\n";
}
else {
cout << "NO\n";
}
}
else { // 公差不为0
if ((b - a) * c >= 0) { // b在等差数列的增长方向上
if ((b - a) % c == 0) { // b可以通过首项a累加公差c得到
cout << "YES\n";
}
else { // b无法通过首项a累加公差c得到
cout << "NO\n";
}
}
else { // b不在等差数列的增长方向上
cout << "NO\n";
}
}
【P2890 [ABC216A] Signed Difficulty】给你一个实数 x . y x.y x.y,其中 y y y 只有一个数码。请你按如下格式输出:若 0 ≤ y ≤ 2 0≤y≤2 0≤y≤2,输出
x-;若 3 ≤ y ≤ 6 3≤y≤6 3≤y≤6,输出x;若 7 ≤ y ≤ 9 7≤y≤9 7≤y≤9,输出x+。
解决这个问题的关键在于将 x x x 和 y y y 分离出来,利用截断特性可以轻松达到目的。
cpp
double a;
cin >> a;
int x = a, y = (a - x) * 10;
事实上,利用 cin 的输入特性,声明两个整型变量 x x x、 y y y,一个字符变量 c 就可以达到目的。更进一步地,利用 scanf 的输入特性,字符变量 c 也无需声明了。
题目告诉我们 y y y 只有一个数码,所以把 y y y 存储到字符型变量中也是可以的。我们知道字符型数据也是通过整数(ASCII 码)存储到计算机中的,也就是说,字符型数据也可以参与关系运算,不过真正参与运算的是字符对应的 ASCII 码。事实上,字符型数据可以参与整型数据允许的任何运算,字符型数据本质上是一种范围较小的整型数据。
cpp
int x;
char y;
cin >> x >> y >> y; // 由于小数点无用,所以直接借用变量 y
if (y <= '2') { // 使用字符变量和常量直接进行关系运算
cout << x << "-\n";
}
else if (y <= '6') {
cout << x << "\n";
}
else {
cout << x << "+\n";
}
接下来我们通过一个例题学习 C++ 中另一种表达分支结构的语句 ------ switch 语句。
【D1017 [OpenJudge] 晶晶赴约会】晶晶的朋友贝贝约晶晶下周一起去看展览,但晶晶每周的 1、3、5 有课必须上课,请帮晶晶判断她能否接受贝贝的邀请,如果能则输出
YES;如果不能则输出NO。注意YES和NO都是大写字母!
首先看一下 switch 语句在解决这个问题时的代码,注意语法格式。
cpp
switch (x) { // x 是输入的一个整数,表明贝贝约晶晶星期 x 去看展览
case 1: // 如果 x 的值是 1,则从这个 case 下面的代码开始执行
cout << "NO\n";
break; // 遇到 break 则结束 switch 语句
case 2: // 如果 x 的值是 2,则从这个 case 下面的代码开始执行
cout << "YES\n";
break; // 遇到 break 则结束 switch 语句
case 3:
cout << "NO\n";
break;
case 4:
cout << "YES\n";
break;
case 5:
cout << "NO\n";
break;
case 6:
cout << "YES\n";
break;
case 7:
cout << "YES\n";
break;
}
与 if 语句不同的是,关键字 switch 后面的小括号中必须是一个整型表达式,即表达式的值必须是整型数据。关键字 case 后面必须是一个整型常量,代表了小括号中整型表达式的不同取值情况。可以把 switch 语句看作一条高速公路,case 语句就是不同的入口。switch 语句会根据表达式的值与 case 的匹配情况选择起始位置。一旦匹配到对应的 case,其余的 case 就失效了,程序会一直执行直到遇见 break 语句,或者执行到 switch 语句末尾。switch 语句在匹配 case 时遵循自上而下的顺序,并不是 case 后面常量表达式的大小顺序。我们可以利用 case 的匹配特性对上面的程序进行简化。
cpp
switch (x) {
case 1:
case 3:
case 5:
cout << "NO\n";
break;
case 2:
case 4:
case 6:
case 7:
cout << "YES\n";
break;
}
根据 case 的匹配特性可以发现,switch 语句不支持范围检测,需要逐个列出所有可能情况,然而这并非易事。因此 switch 语句提供了一个在所有 case 都匹配失败的情况下执行的分支 default,我们可以利用这个特性进一步简化上面的程序。
cpp
switch (x) {
case 1: case 3: case 5: // 执行相同代码的 case 可以写在同一行
cout << "NO\n";
break;
default: // 一般出现在 switch 语句末尾
cout << "YES\n"; // 最后一个分支可以不用添加 break
}