C++ 里的 switch 语句,说白了,就是一种让你根据一个变量的值来决定执行哪段代码的工具。想象一下,你有一堆不同的选择,每种选择对应一个操作,switch 就像一个智能分拣机,能帮你快速找到并执行那个正确的操作。
基础用法:switch 的骨架
我们先从最简单的结构聊起。一个 switch 语句大概长这样:
cpp
switch (表达式) {
case 常量值1:
// 如果表达式的值等于常量值1,执行这里的代码
break; // 跳出 switch 语句
case 常量值2:
// 如果表达式的值等于常量值2,执行这里的代码
break;
default:
// 如果表达式的值和所有 case 都不匹配,执行这里的代码
break; // default 通常也加 break,但不是强制的
}
这里面的“表达式”必须能计算出一个整型、字符型或者枚举类型的值。浮点数是不能直接用在 switch 里的,这点要记住。比如,你不能 switch (3.14)。
case 后面跟着的“常量值”也得是常量,不能是变量。比如,case 10: 是对的,但 case myVariable: 就是错的。而且,每个 case 后面跟着的常量值都必须是唯一的,不能有两个 case 10:。
来看个例子,假设我们想根据用户输入的数字,打印出对应的星期几:
“`cpp
include
int main() {
int day = 4; // 假设今天是星期四
switch (day) {
case 1:
std::cout << "星期一" << std::endl;
break;
case 2:
std::cout << "星期二" << std::endl;
break;
case 3:
std::cout << "星期三" << std::endl;
break;
case 4:
std::cout << "星期四" << std::endl;
break;
case 5:
std::cout << "星期五" << std::endl;
break;
case 6:
std::cout << "星期六" << std::endl;
break;
case 7:
std::cout << "星期日" << std::endl;
break;
default:
std::cout << "输入了无效的数字,请输 1 到 7 之间的数字。" << std::endl;
break;
}
return 0;
}
“`
这段代码会输出“星期四”。 因为 day 的值是 4,它匹配了 case 4,执行了 std::cout << "星期四" << std::endl; 这句代码,然后 break 语句让程序跳出了 switch。
break 的重要性:防止“穿透”
break 语句在 switch 里简直就是灵魂。它的作用是立即终止 switch 语句的执行,把控制权交给 switch 后面的代码。 如果没有 break,会发生什么呢?
我们把上面例子中的 break 语句都去掉,看看结果:
“`cpp
include
int main() {
int day = 4;
switch (day) {
case 1:
std::cout << "星期一" << std::endl;
case 2:
std::cout << "星期二" << std::endl;
case 3:
std::cout << "星期三" << std::endl;
case 4:
std::cout << "星期四" << std::endl; // 匹配到这里
case 5:
std::cout << "星期五" << std::endl; // 会继续执行这里
case 6:
std::cout << "星期六" << std::endl; // 还有这里
case 7:
std::cout << "星期日" << std::endl; // 直到 switch 结束
default:
std::cout << "输入了无效的数字,请输 1 到 7 之间的数字。" << std::endl;
}
return 0;
}
“`
当你运行这段代码,你会看到输出是:
星期四
星期五
星期六
星期日
输入了无效的数字,请输 1 到 7 之间的数字。
看到了吗?程序从 case 4 开始执行,然后一路“穿透”到 default,直到 switch 语句的末尾才停止。 这种现象叫做“fall-through”,也就是“穿透”。大多数时候,这不是我们想要的结果,很容易导致 bug。所以,养成习惯,每个 case 后面都加上 break;,除非你有意要“穿透”。
default 语句:兜底方案
default 语句是可选的,它的作用是当 switch 表达式的值和所有 case 标签都不匹配时,执行 default 后面的代码块。 就像你问了一圈所有的人,都没人能帮忙,最后只能自己动手或者找个“备胎”方案一样。
default 块可以放在 switch 语句的任何位置,但习惯上我们把它放在最后。 即使 default 后面没有 break,程序也会在 default 块执行完后自然跳出 switch,因为 default 通常是 switch 的最后一个分支。但为了代码风格统一,或者未来 default 不再是最后一个分支时避免“穿透”,我个人建议还是加上 break;。
数据类型:switch 的选择范围
前面提到过,switch 的表达式只能是整型、字符型或枚举类型。
- 整型:
int,short,long,long long及其无符号版本都可以。 - 字符型:
char本质上也是整型(存储的是字符的 ASCII 或 Unicode 值),所以也可以用。 - 枚举类型:
enum class(C++11 及以后) 和enum(C 风格枚举) 都行。使用枚举可以提高代码的可读性,比如用enum class Color { Red, Green, Blue };比用魔术数字0, 1, 2要清晰得多。
举个用 char 的例子:
“`cpp
include
int main() {
char grade = ‘B’;
switch (grade) {
case 'A':
std::cout << "优秀" << std::endl;
break;
case 'B':
std::cout << "良好" << std::endl;
break;
case 'C':
std::cout << "及格" << std::endl;
break;
case 'D':
case 'F': // 多个 case 可以共用一个代码块
std::cout << "不及格" << std::endl;
break;
default:
std::cout << "无效的成绩" << std::endl;
break;
}
return 0;
}
“`
这里你会看到 case 'D': case 'F': 这种写法,意思是如果 grade 是 ‘D’ 或者 ‘F’,都会执行“不及格”的代码块。 这也是一种有意为之的“穿透”,但因为 case 'D' 后面没有执行任何操作就直接进入 case 'F' 的代码块,所以通常不会被认为是 bug。
有意“穿透”的场景和 [[fallthrough]] 属性
虽然“穿透”常常是错误,但在某些情况下,它是我们想要的。比如,我们想对一组相似的 case 执行同样的操作,就像上面 case 'D': case 'F': 的例子。
如果一个 case 后面有代码,但你仍然希望它“穿透”到下一个 case,而又不想被编译器警告,C++17 引入了一个 [[fallthrough]] 属性。 这个属性明确告诉编译器和读代码的人,这里的“穿透”是故意的。
用法是这样的:
“`cpp
include
enum class Mode {
Default,
Custom,
Standard
};
void doSomething(Mode mode) {
switch (mode) {
case Mode::Custom:
std::cout << “处理自定义模式的特殊逻辑” << std::endl;
[[fallthrough]]; // 明确声明有意穿透
case Mode::Default:
std::cout << “处理默认模式的通用逻辑” << std::endl;
break;
case Mode::Standard:
std::cout << “处理标准模式的逻辑” << std::endl;
break;
}
}
int main() {
doSomething(Mode::Custom);
std::cout << “—” << std::endl;
doSomething(Mode::Default);
return 0;
}
“`
当 mode 是 Mode::Custom 时,它会先执行自定义逻辑,然后因为 [[fallthrough]],会继续执行 Mode::Default 的通用逻辑。 如果 mode 直接是 Mode::Default,就只执行通用逻辑。
switch 语句内的作用域
在 switch 语句的 case 块中声明变量时,你需要注意作用域问题。如果你在某个 case 里声明了一个变量,并且没有用花括号 {} 把它包起来,那么这个变量的作用域会扩展到 switch 的末尾。这可能导致一个问题:如果你从其他 case “穿透”到这个 case,但跳过了变量的初始化,就会出问题。
为了避免这种麻烦,一个好的做法是,如果你在 case 块里声明变量,最好用花括号把这个 case 的代码块包起来,形成一个局部作用域。
cpp
switch (value) {
case 1: { // 使用花括号创建局部作用域
int x = 10;
std::cout << "x = " << x << std::endl;
break;
}
case 2:
// 这里不能直接访问 x
std::cout << "处理情况 2" << std::endl;
break;
}
switch 与 if-else if:如何选择?
switch 和 if-else if 链都能实现多条件分支判断,但它们各有优势。
什么时候用 switch 呢?
基于单个变量的离散值判断:如果你需要根据一个变量的多个具体、离散的整型、字符型或枚举值来执行不同的代码,switch 是一个很好的选择。 它的代码通常更清晰,更易读,尤其当分支很多时。
性能考量:对于大量的 case 标签,编译器可能会对 switch 语句进行优化,生成一个“跳转表”(jump table),从而实现更快的执行速度。 而 if-else if 链通常是顺序判断,效率可能稍低。
什么时候用 if-else if 呢?
基于范围的条件判断:如果你需要根据某个值的范围(例如,if (score >= 90 && score <= 100))或者复杂的布尔表达式来做判断,if-else if 是唯一选择。switch 不支持这种范围判断。
非整型、字符型或枚举类型的表达式:当你的判断条件不是这些类型时,也只能用 if-else if。
我的经验是,如果条件是针对一个变量的几个固定值,我会优先考虑 switch。它能更直观地表达“输入值到代码块的映射”这种语义。 而且,如果你用枚举类型作为 switch 的条件,有些编译器甚至能帮你检查是否遗漏了某个枚举值对应的 case,这能避免很多潜在的 bug。
最佳实践:让你的 switch 代码更健壮
- 始终包含
default语句:即使你觉得已经覆盖了所有可能的case,也最好有一个default语句来处理意外情况或未预料到的值。 比如,可以在default中输出错误信息或者抛出异常。 - 每个非空
case都加break:除非你明确需要“穿透”,否则每个case块的末尾都要加上break。 如果是有意“穿透”且case块非空,使用[[fallthrough]]属性。 - 使用枚举类型:当
switch表达式涉及一组相关的值时,用enum class或者enum可以大大提高代码的可读性和可维护性。 - 保持
case表达式简洁:case标签后面的常量表达式应该简单明了,不要包含复杂的计算。 - 避免过长的
switch语句:如果switch语句变得非常庞大,包含几十个甚至上百个case,那可能需要考虑重构代码了。 这种情况下,可以考虑使用多态(虚函数)或者工厂模式等面向对象设计模式来替代,让代码结构更清晰。 - 在
case中声明变量时使用花括号:为避免作用域问题,最好在case块内声明变量时加上花括号{}。
switch 语句是一个很实用的控制流工具,用对了能让你的代码简洁高效。但它也有一些“陷阱”,比如“穿透”问题。只要理解了它的工作原理,并遵循一些最佳实践,你就能写出既清晰又健壮的 switch 代码。

七点爱学
评论前必须登录!
立即登录 注册