Menu

C 语言 switch 语句:case、break 与贯穿

C 语言的 switch 语句如何从一组常量中选出一个分支:case 标签,为什么 break 很重要,有意和无意的贯穿,default,对 char 和枚举使用 switch,以及 switch 什么时候比 else-if 链更好。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

一个值,多个常量

一条反复把同一个变量与常量比较的 else if 链,在 C 中有专门的语句来表达:

if (choice == 1)      { /* ... */ }
else if (choice == 2) { /* ... */ }
else if (choice == 3) { /* ... */ }
else                  { /* ... */ }

switch 表达得更直接。它对表达式求值一次,然后直接跳转到匹配的标签。

修改 choice 再运行一次。这种结构让所有合法的取值一目了然,这才是在这里优先使用它的真正原因。

语法与 case 标签的规则

switch (integerExpression) {
    case CONSTANT_1:
        statements
        break;
    case CONSTANT_2:
        statements
        break;
    default:
        statements
        break;
}

C 强制执行四条约束:

  • switch 表达式必须是整数类型:int、char、short、long 或 enum。不能是 float、double,也不能是字符串。
  • 每个 case 标签都必须是编译时已知的常量表达式。case n:(n 是变量)是错误的;case 3 + 4: 和 case MAX:(#define 或枚举常量)则没问题。
  • 任意两个 case 不能有相同的值。重复会导致编译错误,这是一张小而实在的安全网。
  • case 不能表示范围。case 1 ... 5: 是 GCC 扩展,不是标准 C;范围请用 if。

default 是可选的,可以出现在代码块的任何位置,不过几乎所有人都约定把它放在最后。没有 default 时,不匹配的值会直接跳过整个 switch。

break:为什么不能省

C 的 switch 最让人意外的一点是,case 标签是跳转目标,而不是隔间。程序一旦落到某个标签上,就会一直执行它下面的每条语句,越过其他 case 标签,直到遇到 break 或右花括号。

第一个 switch 打印 one、two 和 three:它匹配了 case 1,然后把后面的所有内容都执行了。第二个只打印 one。这种行为叫作贯穿(fall-through),而忘写 break 是经典的 C switch bug:程序不会编译失败,只是悄无声息地出错。

用 gcc -Wall -Wextra -Wimplicit-fallthrough 编译,编译器会指出每一个贯穿到下一个 case 的地方。

最后一个 case 的 break 严格来说是多余的,因为代码块本来就在那里结束。但还是要写上,这样以后在它下面添加新的 case 时,就不会悄悄地产生贯穿。

有意的贯穿

如果是有意为之,贯穿就是一项特性。把多个标签叠在一起、中间不放语句,是表达“这些值执行相同操作”的常规写法:

三个标签共用一段代码,没有重复代码,也没有冗长的 || 条件。编译器不会对这种写法发出警告,因为中间没有可以贯穿的语句。

中间有语句的贯穿,即 case 1 执行完之后有意继续执行 case 2,则比较少见,值得加上明确的注释,因为必须告诉读者漏掉的 break 是刻意的决定:

switch (level) {
    case 3:
        printf("verbose\n");
        /* fall through */
    case 2:
        printf("info\n");
        /* fall through */
    case 1:
        printf("errors\n");
        break;
}

/* fall through */ 是 GCC 警告能识别的注释,所以它也能消除这条诊断信息。

对字符使用 switch

因为 char 是整数类型,用 switch 做字符分类再自然不过:

注意 case 'a': 用的是单引号,表示字符常量,它就是该字符的整数值。case "a": 则是字符串,无法通过编译。

对枚举使用 switch

在 C 中,switch 和枚举是最佳搭档。case 标签变成了可读的名字,而且当 switch 没有处理所有枚举值时,编译器可以发出警告:

这里有两点值得借鉴。每个 case 都直接 return,所以不需要 break:return 会直接离开函数,switch 自然也就结束了。另外这里没有 default:启用 -Wswitch(包含在 -Wall 中)时,不写 default 能让编译器在有人添加第五个状态却忘了改这个函数时发出警告。写了 default 反而会掩盖这个问题。

在 switch 内部声明变量

switch 的主体是一个代码块,所以在某个 case 中的声明,在它后面的 case 中也可见,尽管不一定已经初始化。在 C 中,跳过初始化是错误的:

switch (n) {
    case 1:
        int x = 10;        // error in C: a label cannot precede a declaration this way
        printf("%d\n", x);
        break;
    case 2:
        printf("%d\n", x); // x exists here, but was never initialised
        break;
}

当某个 case 需要局部变量时,用一对花括号把它包起来:

switch (n) {
    case 1: {
        int x = 10;
        printf("%d\n", x);
        break;
    }
    case 2:
        printf("two\n");
        break;
}

用 switch 还是 if-else?

当以下条件全部满足时用 switch:你在判断一个表达式,它是整数类型,并且你在把它与固定常量比较。菜单处理、枚举状态机、字符分类和命令分发都是典型场景。好处是可读性好、能检查重复值,而且编译器能对未处理的枚举值发出警告。

当判断涉及范围(score >= 90)、浮点数、字符串(需要用 strcmp)、多个不同的变量,或用 && 和 || 组合的条件时,用 if-else。这些都无法写成 case 标签。

性能很少是决定因素。编译器会把密集的 switch 转换成跳转表,把稀疏的 switch 转换成一系列比较,所以只有几个 case 时,两种写法没有区别。选择能表达意图的那种。

常见问题

C 语言中 switch 语句是怎么工作的?

switch (expr) 对一个整数表达式求值一次,跳转到常量与之匹配的 case 标签,然后从那里开始执行,直到遇到 break 或 switch 的末尾。如果没有匹配项,有 default 时就跳到那里,否则跳过整个代码块。

C 语言的 switch 为什么需要 break?

因为 case 标签是跳转目标,而不是独立的代码块。没有 break,程序会直接继续执行下一个 case 的语句,这叫作贯穿(fall-through)。漏写 break 是 C 语言中最常见的 switch bug,gcc -Wimplicit-fallthrough 会把它标记出来。

C 语言中能对字符串使用 switch 吗?

不能。C 的 switch 要求整数表达式,所以 int、char 和 enum 值都可以,但字符串、浮点数和范围不行。要根据字符串分支,可以在 if-else 链中用 strcmp,或者先把字符串映射成枚举,再对枚举使用 switch。

C 语言中什么时候应该用 switch 而不是 if-else?

当你把一个整数或字符和一组固定常量比较时用 switch:菜单命令、枚举状态、字符分类。当判断涉及范围、多个变量,或者任何不是与编译期常量做相等比较的条件时,用 if-else 链。

Coddy 编程语言插图

和 Coddy 一起学编程

开始