if 和 switch

悬空 ‘else’ 的问题

1
2
3
4
5
if (y != 0)  
if (x != 0)
result = x / y;
else
printf("Error: y is equal to 0\n");

上面的 else 子句究竟属于哪一个 if 语句呢?

缩进格式暗示它属于最外层的if语句。然而,C语言遵循的规则是 else子句 应该属于离它最近的且还未和其他 else 匹配的 if 语句。

在此例中,else子句实际上属于最内层的if语句,所以正确的缩进格式应该如下所示:

1
2
3
4
5
if (y != 0)  
if (x != 0)
result = x / y;
else
printf("Error: y is equal to 0\n");

如果你希望 else 属于最外层 if ,那就可以把内存的 if 语句用花括号括起来,如下所示:

1
2
3
4
5
if (y != 0)  {
if (x != 0)
result = x / y;
} else
printf("Error: y is equal to 0\n");

所以,为了避免含义表达错误,我们建议用花括号,尽管这看起来可能不美观,但至少保证代码不会出现不必要的bug。况且,C 语言不是通过缩进判断 else 属于哪个 if语句,这不是 Python 语法,而是根据 “else子句 应该属于离它最近的且还未和其他 else 匹配的 if 语句” 来判断的。

switch 语句

1
2
3
4
5
6
7
8
9
switch(表达式) /*首先计算表达式的值*/ 
{
case 常量表达式1:语句1;
case 常量表达式2:语句2;
case 常量表达式3:语句3;
// ……
case 常量表达式n:语句n;
default:语句n+1;
}

C语言不允许有重复的分支标号(即常量表达式),但对分支的顺序没有要求,特别是 default 分支不一定要放置在最后。switch 语句不要求一定有 default 分支。如果 default 不存在,而且控制表达式的值和任何一个分支标号都不匹配的话,控制会直接传给 switch 语句后面的语句。

case后边只可以跟随一个常量表达式。但是,多个分支标号可以放置在同一组语句的前面,代表着这几个常量表达式是等价的,即满足这几个常量表达式任意一个就会执行后面的语句。比方说下面的例子,如果你的 case 是 4,3,2,1,那么都会去执行打印文本内容Passing。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
switch  (grade)  {
case 4:
case 3:
case 2:
case 1:
printf("Passing");
break;
case 0:
printf("Failing");
break;
default:
printf("Illegal grade");
break;
}

为了节省空间,程序员有时还会把几个分支标号放置在同一行中。

1
2
3
4
5
6
7
8
9
10
11
switch  (grade)  {
case 4: case 3: case 2: case 1:
printf("Passing");
break;
case 0:
printf("Failing");
break;
default:
printf("Illegal grade");
break;
}

还有要注意 switch 中的 break语句,如果不恰当使用,就会出现穿透问题。比方说下面这个例子,如果你 case 为0,那么会打印文本内容Failing,但由于后面没有 break语句,就会穿透下去,还会打印文本内容Illegal grade。

1
2
3
4
5
6
7
8
9
10
switch  (grade)  {
case 4: case 3: case 2: case 1:
printf("Passing");
break;
case 0:
printf("Failing");
default:
printf("Illegal grade");
break;
}

忘记使用break语句是编程时常犯的错误。虽然有时会故意忽略 break 以便多个分支共享代码,但通常情况下省略 break 是因为疏忽。

if 和 switch 性能问题

用 if 和 switch 实现如下代码,即根据输入的成绩打印对应的等级:

A 等级: 90-100分

B 等级: 80-89分

C 等级: 70-79分

D 等级: 60-69分

F 等级: 0-59分

先查看 switch 对应的核心汇编代码:

1
2
3
4
5
6
7
8
9
10
11
	switch (score / 10) 对应的汇编代码

007D45AC mov eax,dword ptr [score] ; 将score的值加载到eax寄存器中
007D45AF cdq ; 将EAX的符号位扩展到EDX:EAX
007D45B0 mov ecx,0Ah ; 将10 (0xA)加载到ecx寄存器
007D45B5 idiv ecx ; 用ecx的值除eax的值,商存入eax,余数存入edx
007D45B7 mov dword ptr [ebp-0DCh],eax ; 将商(即 score / 10 的结果)存入 [ebp-0DCh]
007D45BD cmp dword ptr [ebp-0DCh],0Ah ; 比较score / 10的结果和10(即判断是否是满分)
007D45C4 ja $LN14+6h (07D45F1h) ; 如果结果大于10,跳到非法成绩的处理部分
007D45C6 mov edx,dword ptr [ebp-0DCh] ; 将商的值存入edx寄存器
007D45CC jmp dword ptr [edx*4+7D4628h] ; 使用乘法计算地址并跳转到跳转表中的对应位置

switch 会提前把要符合常量表达式地址计算出来,然后直接跳转过去,所以效率会比 if 语句高。if 语句是从上到下按照顺序往下进行判断,直到遇到符合条件的,并非直接调整到指定地址进行代码执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
    if (score >= 90 && score <= 100) {
007D186C cmp dword ptr [score],5Ah
007D1870 jl __$EncStackInitStart+32h (07D187Eh)
007D1872 cmp dword ptr [score],64h
007D1876 jg __$EncStackInitStart+32h (07D187Eh)
grade = 'A';
007D1878 mov byte ptr [grade],41h
}
007D187C jmp __$EncStackInitStart+89h (07D18D5h)
else if (score >= 80 && score < 90) {
007D187E cmp dword ptr [score],50h
007D1882 jl __$EncStackInitStart+44h (07D1890h)
007D1884 cmp dword ptr [score],5Ah
007D1888 jge __$EncStackInitStart+44h (07D1890h)
grade = 'B';
007D188A mov byte ptr [grade],42h
}
007D188E jmp __$EncStackInitStart+89h (07D18D5h)
else if (score >= 70 && score < 80) {
007D1890 cmp dword ptr [score],46h
007D1894 jl __$EncStackInitStart+56h (07D18A2h)
007D1896 cmp dword ptr [score],50h
007D189A jge __$EncStackInitStart+56h (07D18A2h)
grade = 'C';
007D189C mov byte ptr [grade],43h
}
007D18A0 jmp __$EncStackInitStart+89h (07D18D5h)
else if (score >= 60 && score < 70) {
007D18A2 cmp dword ptr [score],3Ch
007D18A6 jl __$EncStackInitStart+68h (07D18B4h)
007D18A8 cmp dword ptr [score],46h
007D18AC jge __$EncStackInitStart+68h (07D18B4h)
grade = 'D';
007D18AE mov byte ptr [grade],44h
}
007D18B2 jmp __$EncStackInitStart+89h (07D18D5h)
else if (score >= 0 && score < 60) {
007D18B4 cmp dword ptr [score],0
007D18B8 jl __$EncStackInitStart+7Ah (07D18C6h)
007D18BA cmp dword ptr [score],3Ch
007D18BE jge __$EncStackInitStart+7Ah (07D18C6h)
grade = 'F';
007D18C0 mov byte ptr [grade],46h
}
007D18C4 jmp __$EncStackInitStart+89h (07D18D5h)
else {
printf("Invalid score\n");
007D18C6 push offset string "Invalid score\n" (07D7B30h)
007D18CB call _printf (07D10CDh)
007D18D0 add esp,4
return;
007D18D3 jmp __$EncStackInitStart+9Bh (07D18E7h)
}

最后,如果 if 和 switch 的分支语句太短,那么性能就没有区别了。