同为条件分支语句,但if和switch的适用范围和性能略有不同。如果将if的适用范围表示为全集,那么switch的适用范围仅仅是它的一个真子集,因为switch只适用于整型表达式,例如int、char、short、long、enum和bool。
既然这样,为何还要switch?只是为了书写美观?当然不是。其实编译器像女朋友一样,默默地为我们做了很多事情。
1. 当case取值范围集中时
当case取值范围相对集中时,编译器会生成一张跳转表(Jump Table),每个表项是采取相应操作的地址。此时,switch花费的时间是常数时间,与case数量无关。
void fun(int i)
{
switch (i)
{
case 0:
i *= 2;
break;
case 2:
i += 2;
break;
case 3:
i += 3;
break;
case 4:
i *= i;
break;
case 5:
i += i;
break;
default:
i = 0;
break;
}
}
fun:
pushl %ebp
movl %esp, %ebp
cmpl $5, 8(%ebp) // 参数i放在%ebp + 8
ja .L2 // 无符号数比较
movl 8(%ebp), %eax
sall $2, %eax // 每个地址为4个字节
addl $.L8, %eax // 基地址
movl (%eax), %eax
jmp *%eax
.L8:
.long .L3
.long .L2
.long .L4
.long .L5
.long .L6
.long .L7
.L3:
sall 8(%ebp)
jmp .L1
.L4:
addl $2, 8(%ebp)
jmp .L1
.L5:
addl $3, 8(%ebp)
jmp .L1
.L6:
movl 8(%ebp), %eax
imull 8(%ebp), %eax
movl %eax, 8(%ebp)
jmp .L1
.L7:
movl 8(%ebp), %eax
addl %eax, %eax
movl %eax, 8(%ebp)
jmp .L1
.L2:
movl $0, 8(%ebp)
nop
.L1:
popl %ebp
ret
从汇编代码可以看到所谓的跳转表,就是从标号L8开始的6个“长”字。每个字的值表示执行目标操作的地址,而L8则可被认为是基地址。
2. 当case取值范围很广时
此时,如果编译器生成跳转表,那么表项的数量会变得庞大,而且有用的表项只有少数几个,因此代价比较大。所以在这种情况下,编译器不会生成跳转表,而是像if...else一样翻译switch。3. 当case数量很多且取值范围很广时
此时,编译器会重新排列case语句,并使用二分查找法加速查找过程。
综上,对于情况1和3,switch的效率明显高于if...else;而对于情况2,其实际效果和if...else一样。