switch 是用来做什么的
switch 把一个值和一组固定的选项进行比较,然后执行匹配的那个分支。当你发现自己在写一长串 if/else if,反复拿同一个变量和不同的常量比较时,用 switch 能更清楚地表达这个意图,而且它常常会被编译成更快的跳转表。
它只能对一个类整数的值和编译期常量做相等性检查。它不能检查范围,不能比较字符串,也不能组合多个条件。遇到这些情况,请继续用 if/else。
基本的 switch
你给 switch 一个值,然后为你关心的值列出 case 标签。每个分支以 break 结尾:
值 3 匹配 case 3:,于是打印出“Wednesday”,然后 break 跳出 switch。default 分支负责兜底:没有任何 case 匹配时它就会执行。它是可选的,但除非你确定每个值都已处理,否则还是写上。
注意,case 标签是后面跟着冒号的常量,而不是条件。你要写 case 3:,绝不能写 case day == 3:。
break 与贯穿
这是 C++ 中关于 switch 最重要的一个陷阱。某个 case 匹配之后,执行不会在下一个 case 处停下,而是一路往下运行,直到遇到 break 或右花括号。看看缺少 break 语句时会发生什么:
你可能以为只会打印“one”。结果四行全都打印了出来:匹配 case 1: 后从那里进入 switch,然后贯穿下面的每一个标签。在每个分支后加上 break;,你就会得到想要的那一行。忘记 break 是“为什么我的 switch 执行了太多代码?”这类 bug 的经典来源。
有意贯穿来合并多个 case
贯穿并不总是错误,它是让几个 case 共用同一段代码的惯用方式。让这些 case 保持为空(没有语句,也没有 break),它们就都会流入下一个代码块:
'A'、'B' 和 'C' 都会落到同一行“Pass”上,因为前两个 case 是空的,会贯穿到第三个。这样写简洁,也是有意为之。如果你确实想在执行一些代码之后再贯穿,就用注释说明,或者在 C++17 及以后使用 [[fallthrough]]; 属性,它告诉编译器“没错,我就是这个意思”,并消除贯穿警告。
对枚举使用 switch
switch 和枚举天生搭配,因为枚举正是“一组固定值中的某一个”。如果你漏掉了某个枚举值,编译器还能发出警告:
使用有作用域的 enum class 时,每个标签都必须加上限定(Direction::East)。因为每个枚举值都已覆盖,所以不需要 default;而且如果你以后加了第五个方向却忘了加对应的 case,很多编译器都会发出警告。编译器的这份帮助,是处理枚举时优先选 switch 而不是 if/else 链的一大原因。
一个陷阱:在 case 中声明变量
你不能在一个 case 中声明带初始化器的变量,又不加作用域就让它在其他 case 中可见。这是一个常见的编译错误:
switch (x) {
case 1:
int n = 10; // error: jump to case 2 bypasses this initialization
cout << n;
break;
case 2:
cout << "two";
break;
}
编译器之所以拒绝它,是因为跳进 case 2: 会跳过 n 的初始化,而 n 此时仍在作用域内。解决办法是用一对花括号把这个 case 的代码包起来,让变量拥有自己的代码块:
只要某个 case 需要自己的局部变量,就给它加上花括号。这样还能防止变量泄漏到下面的 case 中。
下一节:for 循环
switch 和 if 让你的程序选择执行哪段代码,而且只执行一次。但很多工作需要把同一件事做很多遍:计数、遍历列表、重复执行直到某个条件改变。for 循环正是干这件事的主力,也就是下一页的内容。
常见问题
C++ 中什么时候该用 switch 而不是 if-else?
当你要把一个类整数的值和许多固定的常量选项逐一比较时,用 switch,比如菜单选项、星期几的编号或者一个 enum。它比一长串 if/else if 更易读,还能让编译器优化分支跳转。如果条件涉及范围(x > 10)、浮点数、字符串或多个变量,就继续用 if/else:这些 switch 一样都做不到。
为什么 C++ 的 switch 语句需要 break?
一旦某个 case 匹配成功,C++ 会继续执行后面的 case,直到遇到 break 或 switch 的结尾。这叫做贯穿(fall-through)。break 可以阻止它。忘记写 break 是一个经典 bug:你匹配了 case 1:,却意外地把 case 2、case 3 和 default 的代码也执行了。
C++ 中 switch 可以用哪些类型?
只能用整型或枚举类型:int、char、short、long、bool、有作用域或无作用域的 enum,以及任何能转换成这些类型的东西。不能对 double、float 或 std::string 使用 switch,这些情况请用 if/else。每个 case 标签都必须是编译期常量。