鍍金池/ 教程/ C/ 練習(xí)13:Switch語句
練習(xí)9:數(shù)組和字符串
練習(xí)6:變量類型
練習(xí)3:格式化輸出
練習(xí)4:Valgrind 介紹
練習(xí)28:Makefile 進(jìn)階
練習(xí)14:編寫并使用函數(shù)
練習(xí)21:高級數(shù)據(jù)類型和控制結(jié)構(gòu)
練習(xí)20:Zed的強(qiáng)大的調(diào)試宏
練習(xí)18:函數(shù)指針
練習(xí)0:準(zhǔn)備
練習(xí)15:指針,可怕的指針
練習(xí)27:創(chuàng)造性和防御性編程
練習(xí)22:棧、作用域和全局
練習(xí)10:字符串?dāng)?shù)組和循環(huán)
練習(xí)8:大小和數(shù)組
練習(xí)16:結(jié)構(gòu)體和指向它們的指針
練習(xí)7:更多變量和一些算術(shù)
練習(xí)23:認(rèn)識達(dá)夫設(shè)備
練習(xí)12:If,Else If,Else
練習(xí)2:用Make來代替Python
練習(xí)1:啟用編譯器
練習(xí)11:While循環(huán)和布爾表達(dá)式
練習(xí)5:一個C程序的結(jié)構(gòu)
練習(xí)24:輸入輸出和文件
練習(xí)25:變參函數(shù)
練習(xí)13:Switch語句
練習(xí)19:一個簡單的對象系統(tǒng)
練習(xí)26:編寫第一個真正的程序
導(dǎo)言:C的笛卡爾之夢
練習(xí)17:堆和棧的內(nèi)存分配

練習(xí)13:Switch語句

在其它類似Ruby的語言中,switch語句可以處理任意類型的表達(dá)式。一些語言比如Python沒有switch語句,因為帶有布爾表達(dá)式的if語句可以做相同的事情。對于這些語言,switch語句比if語句更加靈活,然而內(nèi)部的機(jī)制是一樣的。

C中的switch語句與它們不同,實際上是一個“跳轉(zhuǎn)表”。你只能夠放置結(jié)果為整數(shù)的表達(dá)式,而不是一些隨機(jī)的布爾表達(dá)式,這些整數(shù)用于計算從swicth頂部到匹配部分的跳轉(zhuǎn)。下面有一段代碼,我要分解它來讓你理解“跳轉(zhuǎn)表”的概念:

#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;
}

在這個程序中我們接受了單一的命令行參數(shù),并且用一種極其復(fù)雜的方式打印出所有原因,來向你演示switch語句。下面是swicth語句的工作原理:

  • 編譯器會標(biāo)記swicth語句的頂端,我們先把它記為地址Y。
  • 接著對switch中的表達(dá)式求值,產(chǎn)生一個數(shù)字。在上面的例子中,數(shù)字為argv[1]中字母的原始的ASCLL碼。
  • 編譯器也會把每個類似case 'A'case代碼塊翻譯成這個程序中距離語句頂端的地址,所以case 'A'就在Y + 'A'處。
  • 接著計算是否Y+letter位于switch語句中,如果距離太遠(yuǎn)則會將其調(diào)整為Y+Default。
  • 一旦計算出了地址,程序就會“跳”到代碼的那個位置并繼續(xù)執(zhí)行。這就是一些case代碼塊中有break而另外一些沒有的原因。
  • 如果輸出了'a',那它就會跳到case 'a',它里面沒有break語句,所以它會貫穿執(zhí)行底下帶有代碼和breakcase 'A'。
  • 最后它執(zhí)行這段代碼,執(zhí)行break完全跳出switch語句塊。

譯者注:更常見的情況是,gcc會在空白處單獨構(gòu)建一張?zhí)D(zhuǎn)表,各個偏移處存放對應(yīng)的case語句的地址。Y不是switch語句的起始地址,而是這張表的起始地址。程序會跳轉(zhuǎn)到*(Y + 'A')而不是Y + 'A'處。

這是對swicth語句工作原理的一個深究,然而實際操作中你只需要記住下面幾條簡單的原則:

  • 總是要包含一個default:分支,可以讓你接住被忽略的輸入。
  • 不要允許“貫穿”執(zhí)行,除非你真的想這么做,這種情況下最好添加一個//fallthrough的注釋。
  • 一定要先編寫casebreak,再編寫其中的代碼。
  • 如果能夠簡化的話,用if語句代替。

你會看到什么

下面是我運(yùn)行它的一個例子,也演示了傳入命令行參數(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語句,當(dāng)沒有提供足夠的參數(shù)時使用return 1返回。返回非0是你提示操作系統(tǒng)程序出錯的辦法。任何大于0的值都可以在腳本中測試,其它程序會由此知道發(fā)生了什么。

如何使它崩潰

破壞一個switch語句塊太容易了。下面是一些方法,你可以挑一個來用:

  • 忘記寫break,程序就會運(yùn)行兩個或多個代碼塊,這些都是你不想運(yùn)行的。
  • 忘記寫default,程序會在靜默中忽略你所忘記的值。
  • 無意中將一些帶有預(yù)料之外的值的變量放入switch中,比如帶有奇怪的值的int。
  • switch中是否未初始化的值。

你也可以使用一些別的方法使這個程序崩潰。試著看你能不能自己做到它。

附加題

  • 編寫另一個程序,在字母上做算術(shù)運(yùn)算將它們轉(zhuǎn)換為小寫,并且在switch中移除所有額外的大寫字母。
  • 使用','(逗號)在for循環(huán)中初始化letter。
  • 使用另一個for循環(huán)來讓它處理你傳入的所有命令行參數(shù)。
  • 將這個switch語句轉(zhuǎn)為if語句,你更喜歡哪個呢?
  • 在“Y”的例子中,我在if代碼塊外面寫了個break。這樣會產(chǎn)生什么效果?如果把它移進(jìn)if代碼塊,會發(fā)生什么?自己試著解答它,并證明你是正確的。