switch语句的一般形式如下:

1
2
3
4
5
switch(表达式){
case 常量表达式:零条或多条语句
case 常量表达式:零条或多条语句
default: 零条或多条语句
}

一个遵循标准的C编译器至少允许一条switch语句中有257个case标签(ANSI C标准),这是为了允许switch满足一个8bit字符的所有情况(256个可能的值加上EOF)。
每个case结构由3部分组成:关键字case;紧随其后的常量值或常量表达式;再紧接一个冒号。看一个例子:

1
2
3
4
5
6
7
8
9
10
11
int main(){
int i=0,j=0;
const int a = 2;
switch(i){
case 1: j=1;
case a: j=2;
case 3: j=3;
default: j=4;
}
return 0;
}

编译出错:
case a: j=2;
error: case label does not reduce to an integer constant
从这里不仅可以看出const修饰的变量并不代表就是常量,而且case后面必须为常量值或常量表达式。
a.当表达式的值与case中的常量匹配时,该case后面的语句就会执行。
b.default(如果有的话)可以出现在case列表的任意位置,但习惯上总是把default放在最后,它在其他的case均无法匹配时被选中执行。
c.如果没有default,而且所有的case均不匹配,那整条switch语句便什么都不做。

许多人可能觉得如果所有的case均不匹配,应该给出一个运行时错误信息,提示无匹配。在C语言中,几乎从来不进行运行时错误检查,运行时检查与C语言的设计理念相违背。按照C语言的理念,程序员应该知道自己正在干什么,而且保证自己的所作所为是正确的。

switch存在的问题:

其一:可以在switch的左花括号之后声明一些变量,但变量不会被初始化。

例如,可以在switch的左花括号之后声明一些变量,从而进行一些局部存储的分配。在最初的编译器里,这是一个技巧——绝大多数用于处理任何复合语句的代码都可以被复用,可以用于处理switch语句中由花括号包住的那部分代码。所以在这个位置上声明一些变量会被编译器很自然的接受,尽管在switch语句中为这些变量加上初始值是没有什么用处的,因为它绝不会被执行——语句从匹配表达式的case开始执行。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
int main(){
int i=0,j=0;
switch(i){
int a = 2;
case 1: j=1;
case 2: j=2;
case 3: j=3;
default: j=4;
printf("%d\n",a);
}
printf("%d\n",j);
return 0;
}

编译时警告:
printf(“%d\n”,a);
warning: ‘a’ is used uninitialized in this function [-Wuninitialized]
运行结果显示:
0
4

在C语言中,当建立一个块时,一般总是这样开始的:
{
语句
}
你总是可以在两者之间增加一些声明,如:
{
声明
语句
}
当分配动态内存代价较高时,你可能会采用这种局部存储的方法,但有可能的话要尽量避免,编译器可以自由的忽略它,它可以通过函数调用来分配所有局部块需要的内存空间。
另一种用法是声明一些完全局部于当前块的变量:

1
2
3
4
5
6
7
8
9
10
int main(){
int a = 2,b = 1;
if(a > b){
int temp = a;
a = b;
b = temp;
}
printf("a = %d, b = %d\n",a,b);
return 0;
}

运行输出:
a = 1, b = 2
若增加代码:

1
2
3
4
5
6
7
8
9
10
11
int main(){
int a = 2,b = 1;
if(a > b){
int temp = a;
a = b;
b = temp;
}
printf("temp = %d\n",temp);
printf("a = %d, b = %d\n",a,b);
return 0;
}

编译出错:
printf(“temp = %d\n”,temp);
error: ‘temp’ undeclared (first use in this function)
可见,变量temp完全局部于if语句的代码块。
C++在这方面又进了一步,允许语句和声明以任意的顺序出现,甚至允许变量的声明出现在for表达式的内部:

1
for(int i = 0; i<100; i++){ ... }

其二:switch内部任何语句都可以加上标签。

switch内部任何语句都可以加上标签,并在执行时跳转到那里,这就有可能破坏程序流的结构化。示例:

1
2
3
4
5
6
7
8
9
10
int main(){
int i = 2;
switch(i){
case 5+3: do_again:
case 2: printf("loop\n");goto do_again;
default: i++;
case 3: ;
}
return 0;
}

上述代码执行时,程序循环输出“loop”。
同时,平时在其他地方,为了避免破坏程序流的结构化,应当尽量不要使用goto语句。

其三:对case可能出现的值太过于放纵了。

所有的case都是可选的,任何形式的语句——包括带标签的语句都是允许的。这就意味着有些错误很难检测出来。代码示例:

1
2
3
4
5
6
7
8
9
10
11
int main(){
int i = 2;
switch(i){
case 5+3: ;
case 2: printf("loop\n");
defau1t: i++;
case 3: ;
}
printf("%d\n",i);
return 0;
}

上述代码将default的字母l变为数字1,编译警告:
defau1t: i++;
warning: label ‘defau1t’ defined but not used [-Wunused-label]
此时default由语句变为一个标签,程序仍然能够执行,执行输出结果:
loop
3

其四:最大的缺点——fall through

switch不会在每个case标签后面的语句执行完毕后自动终止。一旦执行某个case语句,程序将会依次执行后面所有的case,除非遇到break语句。代码示例:

1
2
3
4
5
6
7
8
9
10
int main(){
switch(2){
case 1: printf("case 1\n");
case 2: printf("case 2\n");
case 3: printf("case 3\n");
case 4: printf("case 4\n");
default: printf("default\n");
}
return 0;
}

输出结果为:

1
2
3
4
case 2
case 3
case 4
default

这称之为“fall through”,意思是:如果case语句后面不加break,就依次执行下去,以满足某些特殊情况的要求。但实际上,这是一个非常不好的特性,因为几乎所有的case都需要以break结尾。在大多数情况下,你不希望因这个缺省的行为而不得不加上一条额外的break语句来改变它。

其五:break到底中断了什么。

先看一段有bug的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int main(){
int i = 2,a = 0,b = 1;
switch(i){
case 1:
printf("case 1\n");
break;
case 2:
if(a == 0){
printf("step 1\n");
if(b ==1)
break;
printf("step 2\n");
}//代码意图是跳到这里
printf("step 3\n");
b++;
break;
default: printf("default\n");
}//事实是跳到这里
printf("b = %d\n",b);
return 0;
}

程序输出:
step 1
b = 1
所以,从上面可以体会到,break语句跳出的是最近的那层循环语句或switch语句。if中的break语句使得代码直接跳出switch语句。在C语言中,不要低估”break“语句对控制结构的影响,慎重使用。
故综上,switch语句的一般形式为:

1
2
3
4
5
6
7
8
9
10
switch(表达式){
case 常量表达式:
零条或多条语句;
break;
case 常量表达式:
零条或多条语句;
break;
default:
零条或多条语句;
}