29、switch 语句的穿透:break 忘了写会怎样?
说实话,switch 语句的穿透问题,是我在面试新人时最喜欢问的一个点。
为什么?因为十个人里有八个都在这上面栽过跟头。我自己刚入行那会儿,也因为这个 bug 熬过一个通宵。
一、什么是“穿透”?
先看一段代码。你猜猜输出是什么?
int score = 85;
switch (score / 10) {
case 10:
case 9:
printf("优秀\n");
case 8:
printf("良好\n");
case 7:
printf("中等\n");
case 6:
printf("及格\n");
default:
printf("不及格\n");
}
很多人以为只输出“良好”。
实际输出是:
良好
中等
及格
不及格
看到了吗?从 case 8 开始,一路往下执行到底。这就是 switch 的“穿透”特性。
核心概念:switch 语句中的 case 只是入口标签,不是隔离墙。一旦匹配成功,就会从那个入口一直执行到 switch 结束,除非遇到 break。
二、穿透是怎么发生的?
switch 语句的底层实现,说白了就是一张跳转表。CPU 根据表达式的值,直接跳到对应的 case 地址开始执行。
它不会在每个 case 后面自动停下来。你想想看,如果每个 case 后面都隐含一个 break,那反而会限制很多灵活用法。
我画了一张图,帮你理解这个流程:
三、穿透的两种场景
1. 忘记写 break(bug)
这是最常见的情况。我见过最离谱的一次,是同事在 case 分支里调了一个函数,函数返回后直接掉进了下一个 case 的逻辑里。那个 bug 在线上跑了两个月才被发现。
警告:忘记写 break 不会报错,编译器也不会警告。这是 C 语言里最安静的杀手之一。
2. 故意利用穿透(feature)
穿透不全是坏事。有些场景下,我们故意利用它来简化代码。
// 多个 case 共享同一段逻辑
switch (cmd) {
case 'A':
case 'B':
case 'C':
printf("处理 A/B/C 类命令\n");
break;
case 'D':
printf("处理 D 类命令\n");
break;
default:
printf("未知命令\n");
break;
}
你看,case 'A'、case 'B'、case 'C' 三个入口共享同一段处理代码。这样写比用 if-else 链清爽多了。
四、避坑指南
我曾经在一个通信协议解析模块里,因为少写了一个 break,导致设备收到了错误的下行指令。那次事故让我养成了几个习惯:
- 每个 case 写完立刻写 break——哪怕只有一行代码,也先把 break 写上,再回来填逻辑。
- 故意穿透的地方加注释——比如
// fall through,告诉看代码的人:我是故意的。 - default 分支永远不省略——哪怕你确信所有情况都覆盖了,也写个 default 兜底。我见过太多因为枚举值新增而导致的穿透 bug。
小技巧:有些编译器(如 GCC)支持 -Wimplicit-fallthrough 警告选项。打开它,编译器会帮你标记那些没有注释的穿透 case。我个人习惯在项目里强制开启这个选项。
五、break 与 if-else 的对比
| 特性 | switch + break | if-else 链 |
|---|---|---|
| 可读性 | 分支清晰,一目了然 | 嵌套多了容易乱 |
| 性能 | 跳转表,O(1) 匹配 | 顺序比较,O(n) |
| 灵活性 | 只能做等值比较 | 支持范围、条件组合 |
| 穿透风险 | 忘记 break 会出 bug | 天然隔离,无穿透 |
我个人习惯是:能用 switch 的地方尽量用 switch。代码更干净,性能也更好。但前提是——别忘了 break。
六、总结
switch 的穿透,说白了就是一把双刃剑。
用好了,代码简洁优雅。用不好,就是埋雷。
记住一句话:每个 case 要么有 break,要么有注释。做到这一点,你就能避开 90% 的穿透陷阱。
最后送你一个口诀:
case 入口像大门,break 才是关门人。
故意穿透加注释,default 兜底别忘跟。
公众号:蓝海资料掘金营,微信 deep3321