練習13:Switch語句
原文:Exercise 13: Switch Statement
譯者:飛龍
在其它類似Ruby的語言中,switch
語句可以處理任意型別的表示式。一些語言比如Python沒有switch
語句,因為帶有布林表示式的if
語句可以做相同的事情。對於這些語言,switch
語句比if
語句更加靈活,然而內部的機制是一樣的。
C中的switch
語句與它們不同,實際上是一個“跳轉表”。你只能夠放置結果為整數的表示式,而不是一些隨機的布林表示式,這些整數用於計算從swicth
頂部到匹配部分的跳轉。下面有一段程式碼,我要分解它來讓你理解“跳轉表”的概念:
#include <stdio.h>
int main(int argc, char *argv[])
{
if(argc != 2) {
printf("ERROR: You need one argument.
");
// this is how you abort a program
return 1;
}
int i = 0;
for(i = 0; argv[1][i] != ` `; i++) {
char letter = argv[1][i];
switch(letter) {
case `a`:
case `A`:
printf("%d: `A`
", i);
break;
case `e`:
case `E`:
printf("%d: `E`
", i);
break;
case `i`:
case `I`:
printf("%d: `I`
", i);
break;
case `o`:
case `O`:
printf("%d: `O`
", i);
break;
case `u`:
case `U`:
printf("%d: `U`
", i);
break;
case `y`:
case `Y`:
if(i > 2) {
// it`s only sometimes Y
printf("%d: `Y`
", i);
}
break;
default:
printf("%d: %c is not a vowel
", i, letter);
}
}
return 0;
}
在這個程式中我們接受了單一的命令列引數,並且用一種極其複雜的方式列印出所有原因,來向你演示switch
語句。下面是swicth
語句的工作原理:
-
編譯器會標記
swicth
語句的頂端,我們先把它記為地址Y。 -
接著對
switch
中的表示式求值,產生一個數字。在上面的例子中,數字為argv[1]
中字母的原始的ASCLL碼。 -
編譯器也會把每個類似
case `A`
的case
程式碼塊翻譯成這個程式中距離語句頂端的地址,所以case `A`
就在Y + `A`
處。 -
接著計算是否
Y+letter
位於switch
語句中,如果距離太遠則會將其調整為Y+Default
。 -
一旦計算出了地址,程式就會“跳”到程式碼的那個位置並繼續執行。這就是一些
case
程式碼塊中有break
而另外一些沒有的原因。 -
如果輸出了
`a`
,那它就會跳到case `a`
,它裡面沒有break
語句,所以它會貫穿執行底下帶有程式碼和break
的case `A`
。 -
最後它執行這段程式碼,執行
break
完全跳出switch
語句塊。
譯者注:更常見的情況是,gcc會在空白處單獨構建一張跳轉表,各個偏移處存放對應的
case
語句的地址。Y不是switch
語句的起始地址,而是這張表的起始地址。程式會跳轉到*(Y + `A`)
而不是Y + `A`
處。
這是對swicth
語句工作原理的一個深究,然而實際操作中你只需要記住下面幾條簡單的原則:
-
總是要包含一個
default:
分支,可以讓你接住被忽略的輸入。 -
不要允許“貫穿”執行,除非你真的想這麼做,這種情況下最好新增一個
//fallthrough
的註釋。 -
一定要先編寫
case
和break
,再編寫其中的程式碼。 -
如果能夠簡化的話,用
if
語句代替。
你會看到什麼
下面是我執行它的一個例子,也演示了傳入命令列引數的不同方法:
$ make ex13
cc -Wall -g ex13.c -o ex13
$ ./ex13
ERROR: You need one argument.
$
$ ./ex13 Zed
0: Z is not a vowel
1: `E`
2: d is not a vowel
$
$ ./ex13 Zed Shaw
ERROR: You need one argument.
$
$ ./ex13 "Zed Shaw"
0: Z is not a vowel
1: `E`
2: d is not a vowel
3: is not a vowel
4: S is not a vowel
5: h is not a vowel
6: `A`
7: w is not a vowel
$
記住在程式碼的開始有個if
語句,當沒有提供足夠的引數時使用return 1
返回。返回非0是你提示作業系統程式出錯的辦法。任何大於0的值都可以在指令碼中測試,其它程式會由此知道發生了什麼。
如何使它崩潰
破壞一個switch
語句塊太容易了。下面是一些方法,你可以挑一個來用:
-
忘記寫
break
,程式就會執行兩個或多個程式碼塊,這些都是你不想執行的。 -
忘記寫
default
,程式會在靜默中忽略你所忘記的值。 -
無意中將一些帶有預料之外的值的變數放入
switch
中,比如帶有奇怪的值的int
。 -
在
switch
中是否未初始化的值。
你也可以使用一些別的方法使這個程式崩潰。試著看你能不能自己做到它。
附加題
-
編寫另一個程式,在字母上做算術運算將它們轉換為小寫,並且在
switch
中移除所有額外的大寫字母。 -
使用
`,`
(逗號)在for
迴圈中初始化letter
。 -
使用另一個
for
迴圈來讓它處理你傳入的所有命令列引數。 -
將這個
switch
語句轉為if
語句,你更喜歡哪個呢? -
在“Y”的例子中,我在
if
程式碼塊外面寫了個break
。這樣會產生什麼效果?如果把它移進if
程式碼塊,會發生什麼?自己試著解答它,並證明你是正確的。