简单的说 switch...case与if...else的根本区别在于,switch...case会生成一个跳转表来指示实际的case分支的地址,而这个跳转表的索引号与switch变量的值是相等的。从而,switch...case不用像if...else那样遍历条件分支直到命中条件,而只需访问对应索引号的表项从而到达定位分支的目的。所以从效率上来说由于if...else的遍历性,代码执行效率是不高的。 |
复杂的说
-
1.switch...case结构的汇编表示
写入switch...case结构的代码:
int fun(char c) { char res; switch(c) { case 'a': res='a'; break; case 'e': res='e'; break; case 'i': res='i'; break; case 'o': res='o'; break; case 'u': res='u'; break; default: res=' '; }
用gcc产生的汇编代码是:
00000000 <fun>: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 83 ec 14 sub $0x14,%esp 6: 8b 45 08 mov 0x8(%ebp),%eax 9: 88 45 ec mov %al,-0x14(%ebp) c: 0f be 45 ec movsbl -0x14(%ebp),%eax 10: 83 e8 61 sub $0x61,%eax 13: 83 f8 14 cmp $0x14,%eax 16: 77 27 ja 3f <fun+0x3f> 18: 8b 04 85 00 00 00 00 mov 0x0(,%eax,4),%eax 1f: ff e0 jmp *%eax 21: c6 45 ff 61 movb $0x61,-0x1(%ebp) 25: eb 1c jmp 43 <fun+0x43> 27: c6 45 ff 65 movb $0x65,-0x1(%ebp) 2b: eb 16 jmp 43 <fun+0x43> 2d: c6 45 ff 69 movb $0x69,-0x1(%ebp) 31: eb 10 jmp 43 <fun+0x43> 33: c6 45 ff 6f movb $0x6f,-0x1(%ebp) 37: eb 0a jmp 43 <fun+0x43> 39: c6 45 ff 75 movb $0x75,-0x1(%ebp) 3d: eb 04 jmp 43 <fun+0x43> 3f: c6 45 ff 20 movb $0x20,-0x1(%ebp) 43: 0f be 45 ff movsbl -0x1(%ebp),%eax 47: c9 leave 48: c3 ret
计算机中处理switch...case结构时,会生成跳转表,根据变量的取值跳转到合适的分支。在上面的例子中,变量c存放在ebp偏移8字节的位置,它首先减去97,如果大于20则跳转到默认分支。在执行完对应的分支后,每个分支后都有jmp43语句用于跳转到函数的结尾。
2.if...else语句的汇编表示
上面的代码写成if...else的形式,产生的汇编代码是:
00000000 <fun>: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 83 ec 14 sub $0x14,%esp 6: 8b 45 08 mov 0x8(%ebp),%eax 9: 88 45 ec mov %al,-0x14(%ebp) c: 80 7d ec 61 cmpb $0x61,-0x14(%ebp) 10: 75 06 jne 18 <fun+0x18> 12: c6 45 ff 61 movb $0x61,-0x1(%ebp) 16: eb 34 jmp 4c <fun+0x4c> 18: 80 7d ec 65 cmpb $0x65,-0x14(%ebp) 1c: 75 06 jne 24 <fun+0x24> 1e: c6 45 ff 65 movb $0x65,-0x1(%ebp) 22: eb 28 jmp 4c <fun+0x4c> 24: 80 7d ec 69 cmpb $0x69,-0x14(%ebp) 28: 75 06 jne 30 <fun+0x30> 2a: c6 45 ff 69 movb $0x69,-0x1(%ebp) 2e: eb 1c jmp 4c <fun+0x4c> 30: 80 7d ec 6f cmpb $0x6f,-0x14(%ebp) 34: 75 06 jne 3c <fun+0x3c> 36: c6 45 ff 6f movb $0x6f,-0x1(%ebp) 3a: eb 10 jmp 4c <fun+0x4c> 3c: 80 7d ec 75 cmpb $0x75,-0x14(%ebp) 40: 75 06 jne 48 <fun+0x48> 42: c6 45 ff 75 movb $0x75,-0x1(%ebp) 46: eb 04 jmp 4c <fun+0x4c> 48: c6 45 ff 20 movb $0x20,-0x1(%ebp) 4c: 0f be 45 ff movsbl -0x1(%ebp),%eax 50: c9 leave 51: c3 ret
可见,如果输入了字符a,这个程序将不需要跳转,这时if...else的效率要高于switch...case.如果是输入了字符o或者u,程序将多次跳转,程序的分支变多时,必然会导致效率降低。
3.switch...case语句和if...else效率比较
这里不考虑编译器的优化和条件传送(条件传送介绍),仅仅从跳转次数来比较两者的效率。
switch...case结构中有跳转表,输入的字符只要经过一次比较就可以正确的找到跳转分支,所以平均情况下跳转次数为1.
if...else结构如果有n个分支,分别记为n0,n1,n2,n3,...n(i-1),每个分支出现的概率假设未pi,分别为p0,p1,p2,p3,...p(i-1)。执行第一分支前不需要跳转,其它分支均需要跳转,执行第二个分支之前需要跳转一次,第三个分支需要跳转两次...需要跳转平均跳转的次数s为:
如果
>1,则此时if...case的效率是小于switch...case的,如果它的值小于1,此时if...else的效率高于switch...case。
假设if...else分支的每个分支出现的概率相同,即1/n,上面的跳转次数的期望值可以改写为:
。
如果n=3,则期望的跳转次数为1,刚好与switch...case相同。由此,如果选择分支大于3的时候,选用switch...case结构效率会更高一些,而小于3时,选用if...else结构更好。
总结:在选择分支较多时,选用switch...case结构会提高程序的效率,但switch不足的地方在于只能处理字符或者数字类型的变量,if...else结构更加灵活一些,if...else结构可以用于判断表达式是否成立,比如if(a+b>c),if...else的应用范围更广,switch...case结构在某些情况下可以替代if...else结构。