悬空 ‘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 的分支语句太短,那么性能就没有区别了。