8-5 switch语句基础
虽然可以将多个if-else语句串联起来,但这种做法既难以阅读又效率低下。请看以下程序:
#include <iostream>
void printDigitName(int x)
{
if (x == 1)
std::cout << "One";
else if (x == 2)
std::cout << "Two";
else if (x == 3)
std::cout << "Three";
else
std::cout << "Unknown";
}
int main()
{
printDigitName(2);
std::cout << '\n';
return 0;
}

在printDigitName()函数中,变量x将根据传入的值最多被求值三次(这效率低下),且读者必须确保每次求值的都是x(而非其他变量)。
由于变量或表达式与多组不同值进行相等性测试的情况很常见,C++为此提供了专用的替代条件语句——switch语句。以下是使用switch实现的同等功能程序:
#include <iostream>
void printDigitName(int x)
{
switch (x)
{
case 1:
std::cout << "One";
return;
case 2:
std::cout << "Two";
return;
case 3:
std::cout << "Three";
return;
default:
std::cout << "Unknown";
return;
}
}
int main()
{
printDigitName(2);
std::cout << '\n';
return 0;
}

switch语句switch statement的原理很简单:先求值一个表达式(有时称为条件)以产生一个值。
随后会发生以下情况之一:
- 若表达式的值等于任何case标签后的值,则执行匹配case标签后的语句。
- 若未找到匹配值且存在默认标签,则执行默认标签后的语句。
- 若未找到匹配值且不存在默认标签,则跳过该switch语句。
下面我们将详细探讨这些概念。
启动switch语句
我们使用 switch 关键字启动switch语句,随后用括号括起需要在内部求值的条件表达式。通常该表达式仅包含单个变量,但也可以是任何有效的表达式。
开关语句中的条件必须求值为整数的类型(若需回顾哪些基本类型属于整数的类型,请参阅第4.1课——基础数据类型介绍),或枚举类型(将在后续第13.2课——无作用域枚举与第13.6课——作用域枚举(枚举类)中讲解),或可转换为此类类型。求值结果为浮点类型、字符串及大多数其他非整数的类型的表达式在此处不可使用。
对于进阶读者
为什么switch语句只允许使用整型的(或枚举型)类型?答案在于switch语句的设计初衷就是高度优化。历史上,编译器实现switch语句最常见的方式是通过跳转表——而跳转表只能处理整数的值。
对于熟悉数组的读者,跳转表的工作原理类似数组:整数的值作为数组索引,可直接“跳转”至对应结果。这种方式远比逐个顺序比较高效。
当然,编译器并非必须使用跳转表实现 switch 语句,有时也会采用其他方式。从技术角度而言,C++ 完全可以放宽限制允许使用其他类型,只是目前(截至 C++23 标准)尚未实现这一变更。
在条件表达式之后,我们声明一个代码块。在代码块内部,我们使用标签来定义所有需要进行相等性测试的值。switch语句中使用的标签有两种类型,我们将在后续部分进行讨论。
Case 标签
第一类标签是case 标签case label,使用case关键字声明,后跟constant表达式。该constant表达式必须与条件类型匹配,或可转换为该类型。
若条件表达式的值等于 case标签 后的表达式,则从该 case标签 后的首个语句开始执行,并按顺序继续执行。
以下是条件与案例标签匹配的示例:
#include <iostream>
void printDigitName(int x)
{
switch (x) // x is evaluated to produce value 2
{
case 1:
std::cout << "One";
return;
case 2: // which matches the case statement here
std::cout << "Two"; // so execution starts here
return; // and then we return to the caller
case 3:
std::cout << "Three";
return;
default:
std::cout << "Unknown";
return;
}
}
int main()
{
printDigitName(2);
std::cout << '\n';
return 0;
}
此代码输出:

在上面的程序中,x 的计算结果为 2。由于存在值为 2 的 case标签,执行流程跳转到该 case标签 对应的语句。程序输出 Two 后,执行 return 语句返回调用方。
case标签 的数量没有实际限制,但 switch 语句中的所有 case标签 必须唯一。也就是说,不能这样写:
switch (x)
{
case 54:
case 54: // error: already used value 54!
case '6': // error: '6' converts to integer value 54, which is already used
}

如果条件表达式与任何案例标签都不匹配,则不会执行任何案例。我们稍后将展示一个示例。
默认标签
第二种标签是默认标签default label(常称为默认casedefault case),使用default关键字声明。若条件表达式与任何情况标签都不匹配且存在默认标签,则执行从 默认标签 后的第一条语句开始。
以下是条件匹配 默认标签 的示例:
#include <iostream>
void printDigitName(int x)
{
switch (x) // x is evaluated to produce value 5
{
case 1:
std::cout << "One";
return;
case 2:
std::cout << "Two";
return;
case 3:
std::cout << "Three";
return;
default: // which does not match to any case labels
std::cout << "Unknown"; // so execution starts here
return; // and then we return to the caller
}
}
int main()
{
printDigitName(5);
std::cout << '\n';
return 0;
}
此代码输出:

默认标签 是可选的,每个switch语句中只能有一个 默认标签。按惯例,默认情况应置于switch代码块的末尾。
最佳实践
将默认情况放在switch块的最后。
无匹配的 case标签 且未提供 默认case
若条件表达式的值与任何case标签均不匹配,且未提供 默认case,则switch语句块内的所有case语句均不执行。程序执行将跳过switch块继续进行。
#include <iostream>
void printDigitName(int x)
{
switch (x) // x is evaluated to produce value 5
{
case 1:
std::cout << "One";
return;
case 2:
std::cout << "Two";
return;
case 3:
std::cout << "Three";
return;
// no matching case exists and there is no default case
}
// so execution continues here
std::cout << "Hello";
}
int main()
{
printDigitName(5);
std::cout << '\n';
return 0;
}

在上例中,x 求值为 5,但没有与 5 匹配的 case标签,也没有 默认case。因此,没有cases被执行。执行流程跳过 switch 块继续,最终输出 Hello。
执行 break 语句
在上面的示例中,我们使用 return 语句来停止标签后语句的执行。但这也会退出整个函数。
break语句break statement(使用 break 关键字声明)会告知编译器:我们已完成 switch 块内语句的执行,应继续执行 switch 块结束后的语句。这使我们能够退出 switch 语句而不退出整个函数。
以下示例将 return 替换为 break 进行重写:
#include <iostream>
void printDigitName(int x)
{
switch (x) // x evaluates to 3
{
case 1:
std::cout << "One";
break;
case 2:
std::cout << "Two";
break;
case 3:
std::cout << "Three"; // execution starts here
break; // jump to the end of the switch block
default:
std::cout << "Unknown";
break;
}
// execution continues here
std::cout << " Ah-Ah-Ah!";
}
int main()
{
printDigitName(3);
std::cout << '\n';
return 0;
}
上述示例输出:

最佳实践
标签下方的每组语句都应以break语句或return语句结束。这包括switch语句中最后一个标签下方的语句。
那么,如果在标签下的语句块末尾没有使用break或return语句结束,会发生什么情况呢?我们将在下一课中探讨这个话题及其他相关内容。
标签通常不缩进
在第2.9课——命名冲突与命名空间介绍中,我们提到代码通常会缩进一级以帮助识别其属于嵌套作用域区域。由于switch语句的波浪括号定义了新的作用域区域,我们通常会将括号内的所有内容缩进一级。
然而标签本身并不定义嵌套作用域。因此标签后面的代码通常不缩进。
但若将标签及其后续语句统一缩进相同层级,最终效果如下所示:
// Unreadable version
void printDigitName(int x)
{
switch (x)
{
case 1:
std::cout << "One";
return;
case 2:
std::cout << "Two";
return;
case 3:
std::cout << "Three";
return;
default:
std::cout << "Unknown";
return;
}
}
这使得很难确定每个案例的起止位置。
我们有两种选择。首先,我们可以无论如何都缩进标签后的语句:
// Acceptable but not preferred version
void printDigitName(int x)
{
switch (x)
{
case 1: // indented from switch block
std::cout << "One"; // indented from label (misleading)
return;
case 2:
std::cout << "Two";
return;
case 3:
std::cout << "Three";
return;
default:
std::cout << "Unknown";
return;
}
}
虽然这种写法比之前的版本更易读,但它暗示每个标签下的语句处于嵌套作用域中,实际并非如此(下节课我们将看到相关示例:在某个分支定义的变量可在另一个分支使用)。这种格式虽被视为可接受(因其可读性),但并非首选。
传统做法是标签不缩进:
// Preferred version
void printDigitName(int x)
{
switch (x)
{
case 1: // not indented from switch statement
std::cout << "One";
return;
case 2:
std::cout << "Two";
return;
case 3:
std::cout << "Three";
return;
default:
std::cout << "Unknown";
return;
}
}
这使得每个标签都易于识别。由于这些语句仅比switch块缩进一层,因此正确地暗示了它们都属于switch块的作用域。
在后续课程中,我们将遇到其他类型的标签——出于相同原因,这些标签也通常不缩进。
最佳实践
建议不要缩进标签。这样既能让标签从周围代码中突出显示,又不会暗示它们定义了嵌套作用域区域。
switch 语句与 if-else 语句的对比
当需要将单个表达式(非布尔整数的类型或枚举类型)与少量值进行相等性比较时,switch 语句最为适用。若 case 标签数量过多,switch 语句将难以阅读。
相较于等效的if-else语句,switch语句更具可读性,能清晰体现每个分支测试的是同一表达式,且具有仅求值一次表达式的优势(提升效率)。
但if-else的灵活性显著更高。以下场景通常更适合使用if或if-else:
- 测试非相等比较(如x > 5)
- 需要同时满足多个条件(例如 x == 5 && y == 6)
- 判断值是否在特定区间内(例如 x >= 5 && x <= 10)
- 表达式类型不被 switch 支持(例如 d == 4.0)
- 表达式最终求值结果为布尔值
最佳实践
当需要将单个表达式(非布尔整数的类型或枚举类型)与少量值进行相等性比较时,应优先使用switch语句而非if-else语句。

浙公网安备 33010602011771号