# 練習13:Switch語句
> 原文:[Exercise 13: Switch Statement](http://c.learncodethehardway.org/book/ex13.html)
> 譯者:[飛龍](https://github.com/wizardforcel)
在其它類似Ruby的語言中,`switch`語句可以處理任意類型的表達式。一些語言比如Python沒有`switch`語句,因為帶有布爾表達式的`if`語句可以做相同的事情。對于這些語言,`switch`語句比`if`語句更加靈活,然而內部的機制是一樣的。
C中的`switch`語句與它們不同,實際上是一個“跳轉表”。你只能夠放置結果為整數的表達式,而不是一些隨機的布爾表達式,這些整數用于計算從`swicth`頂部到匹配部分的跳轉。下面有一段代碼,我要分解它來讓你理解“跳轉表”的概念:
```c
#include <stdio.h>
int main(int argc, char *argv[])
{
if(argc != 2) {
printf("ERROR: You need one argument.\n");
// this is how you abort a program
return 1;
}
int i = 0;
for(i = 0; argv[1][i] != '\0'; i++) {
char letter = argv[1][i];
switch(letter) {
case 'a':
case 'A':
printf("%d: 'A'\n", i);
break;
case 'e':
case 'E':
printf("%d: 'E'\n", i);
break;
case 'i':
case 'I':
printf("%d: 'I'\n", i);
break;
case 'o':
case 'O':
printf("%d: 'O'\n", i);
break;
case 'u':
case 'U':
printf("%d: 'U'\n", i);
break;
case 'y':
case 'Y':
if(i > 2) {
// it's only sometimes Y
printf("%d: 'Y'\n", i);
}
break;
default:
printf("%d: %c is not a vowel\n", 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`語句代替。
## 你會看到什么
下面是我運行它的一個例子,也演示了傳入命令行參數的不同方法:
```sh
$ 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`代碼塊,會發生什么?自己試著解答它,并證明你是正確的。
- 笨辦法學C 中文版
- 前言
- 導言:C的笛卡爾之夢
- 練習0:準備
- 練習1:啟用編譯器
- 練習2:用Make來代替Python
- 練習3:格式化輸出
- 練習4:Valgrind 介紹
- 練習5:一個C程序的結構
- 練習6:變量類型
- 練習7:更多變量和一些算術
- 練習8:大小和數組
- 練習9:數組和字符串
- 練習10:字符串數組和循環
- 練習11:While循環和布爾表達式
- 練習12:If,Else If,Else
- 練習13:Switch語句
- 練習14:編寫并使用函數
- 練習15:指針,可怕的指針
- 練習16:結構體和指向它們的指針
- 練習17:堆和棧的內存分配
- 練習18:函數指針
- 練習19:一個簡單的對象系統
- 練習20:Zed的強大的調試宏
- 練習21:高級數據類型和控制結構
- 練習22:棧、作用域和全局
- 練習23:認識達夫設備
- 練習24:輸入輸出和文件
- 練習25:變參函數
- 練習26:編寫第一個真正的程序
- 練習27:創造性和防御性編程
- 練習28:Makefile 進階
- 練習29:庫和鏈接
- 練習30:自動化測試
- 練習31:代碼調試
- 練習32:雙向鏈表
- 練習33:鏈表算法
- 練習34:動態數組
- 練習35:排序和搜索
- 練習36:更安全的字符串
- 練習37:哈希表
- 練習38:哈希算法
- 練習39:字符串算法
- 練習40:二叉搜索樹
- 練習41:將 Cachegrind 和 Callgrind 用于性能調優
- 練習42:棧和隊列
- 練習43:一個簡單的統計引擎
- 練習44:環形緩沖區
- 練習45:一個簡單的TCP/IP客戶端
- 練習46:三叉搜索樹
- 練習47:一個快速的URL路由
- 后記:“解構 K&R C” 已死