[C 語言] 程式設計教學:使用控制結構 (Control Structure) 改變程式執行順序

【分享本文】
Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

    預設情形下,程式執行的順序是由上至下,但我們可以透過控制結構 (control structure) 來改變程式執行的流程,讓程式有基本的判斷能力。本文介紹 C 語言可用的控制結構。

    if

    if 算是最基礎的選擇結構,由於英文中的 if 的語義相當符合這個情境,幾乎所有的程式語言都保留 if 這個保留字。if 的 C 虛擬碼如下:

    if (condition_a) {
        // Do something_a.
    } else if (condition_b) {
        // Do something_b.
    } else {
        // Do something_c.
    }
    

    以本例來說,若程式符合 condition_a,程式會執行 something_a 內的程式碼,然後跳出整個 if 敘述。若程式不符合 condition_a,會檢查下一個條件,若程式符合 condition_b,則會執行 something_b 內的程式碼,然後跳出整個 if 敘述。若前述條件皆不符合,則會執行 else 區塊內的程式碼。

    除了 if 區塊本身是必需的,else ifelse 都是選擇性的。else if 可以多個,而 else 僅有一個,且需放最後。這用邏輯思考來想即可,不要硬背。

    由於 C 沒有強制空格,else ifelse 可以換行,這種方式很適合搭配註解一起使用:

    // Comment_a
    if (condition_a) {
        // Do something_a.
    }
    // Comment_b
    else if (condition_b) {
        // Do something_b.
    }
    // Comment_c.
    else {
        // Do something_c.
    }

    讀者可從中自行選擇喜好的風格。

    以下是實例:

    #include <stdio.h>
    #include <stdlib.h>
    
    int main()
    {
        // Prompt for input.
        printf("Input an integer: ");
    
        int num;
        // Valid input.
        if (scanf("%d", &num) == 1) {
            if (num % 2 == 0) {
                printf("%d is even\n", num);
            } else {
                printf("%d is odd\n", num);
            }
        }
        // Invalid input.
        else {
            fprintf(stderr, "Invalid input\n");
            return EXIT_FAILURE;
        }
        
        return EXIT_SUCCESS;
    }

    一開始,我們用一個 prompt 引導使用者輸入整數。但我們不能一廂情願地認定輸入的資料格式正確,仍要檢查使用者輸入的內容。以本例來說,若輸入的格式正確,我們就檢查該數字是奇數或偶數;若不正確,則跳出錯誤訊息。

    switch

    switch 算是一種小小的語法糖,主要是用來簡化 if 敘述。switch 的 C 虛擬碼如下:

    switch (value) {
    case a:
        // Do something.
        break;
    case b:
    case c:
        // Fallthrough.
        // Do something.
        break;
    default:
        // Do something.
        break;
    }
    

    要注意在 switch 敘述中,若沒寫 break 則會繼續前往下一個條件,這種特性叫做 *fallthrough*。由於這種特性有時會造成 bug,在 Go 等現代語言中將其修改掉了。

    以下是實例:

    #include <stdio.h>
    #include <time.h>
    
    int main(void)
    {
        // Get the object to `tm *` for current time.
        time_t t = time(NULL);
        struct tm *now = localtime(&t);
    
        switch (now->tm_wday) {
        case 0:  // Sunday.
        case 6:  // Saturday.
            // Fallthrough.
            printf("Weekend\n");
            break;
        case 5:  // Friday.
            printf("Thank God. It's Friday.\n");
            break;
        case 3:  // Wednesday.
            printf("Hump day\n");
            break;
        default:  // All other days of week.
            printf("Week\n");
            break;
        }
    
        return 0;
    }

    一開始,我們建立一個時間物件,接著,取得當下時間。根據當下時間來吐出相對應的訊息,藉由 switch 來進行判斷。讀者目前不用在糾結在時間物件的使用細節,目前就當固定用法即可,學到指標後自然會這種寫法。

    switchif 是相通的,讀者可試著用 if 改寫這個例子。

    while

    while 是一種未定次數的迴圈,主要是用來反覆執行某一區塊的程式碼。while 的 C 虛擬碼如下:

    while (condition) {
        // Do something.
    }
    

    condition 的狀況為真時,就會執行區塊內的程式碼;反之,則不執行。

    以下是實例:

    #include <stdio.h>
    
    int main(void)
    {
        int i = 10;
        
        while (i > 0) {
            printf("Counting down %d\n", i);
            
            i--;
        }
        
        printf("Blast\n");
    
        return 0;
    }

    如果寫成 while(1) 或是 while(true) 等,代表該迴圈是無限迴圈。C 虛擬碼如下:

    // Run infinitely.
    while (1) {
      // Do something.
    }
    

    初學者可能會在無意間寫出無限迴圈,這也算是一種常見的 bug。不過,無限迴圈並不是什麼可怕的事,電腦遊戲的 game loop 或是視窗程式的 event loop 基本上都是某種無限迴圈。無限迴圈會搭配 break 敘述以跳出此迴圈,詳見下文。

    do ... while

    do ... while 算是 while 的變體,和 while 不同的是 do ... while 至少會執行一次。其虛擬碼如下:

    do {
    
    } while (condition);
    

    相對於 while 來說,do ... while 比較少用,一些使用的時機像是減少重覆程式碼。

    有一個小技巧是用 do ... while(0) 執行單次迴圈,像是以下 C 虛擬碼:

    do {
        // do some things.
    
        if (error) {
            break;
        }
    
        // do more things.
    } while (0);
    

    這個小技巧會有用是因為我們不能在 if 敘述中加上 break,但在 while 中可以。在這個技巧中,我們實質上是使用一個可以加上 break 的單一區塊,這樣可以簡化錯誤處理的步驟。

    for

    for 迴圈是用來執行有特定次數的程式區塊,在 C 語言中,for 只有一種形式,就是用計數器來控制。以下是 for 的虛擬碼:

    for (start; end; adjustment) {
        // Do something.
    }
    

    start 中要將計數器初始化,end 為計數中止條件,adjustment 為每次調整計數器的步驟。

    其實 for 可以改寫成等義的 while

    start;
    while (end) {
        // Do something.
        
        adjustment;
    }
    

    for 改寫成等義的 while 或反過來也是一種基本的程式練習,讀者可自行嘗試。

    我們將先前 while 的例子改寫:

    #include <stdio.h>
    
    int main(void)
    {
        // C99
        for (int i = 10; i > 0; i--) {
            printf("Counting down %d\n", i);
        }
        
        printf("Blast\n");
    
        return 0;
    }

    要注意在 for 條件內初始化變數的方法僅限 C99 後可用,在 C89 前只能用以下寫法:

    int i;
    for (i = 0; i < 10; i++) {
        // Do something.
    }

    但是命名空間中會多一個 i 變數,而 i 又是迴圈中慣用的變數。如果為了某些原因需停留在 C89,可將整個 for 敘述包在區塊中以解決命名空間汙染的問題:

    {
        int i;
        for (i = 0; i < 10; i++) {
            // Do something.
        }
    }

    for (;;) 也代表無限迴圈:

    // Run infinitely.
    for (;;) {
        // Do something.
    }

    但語義上不若 while (true) 自然。

    continuebreak

    這兩種語法算是限縮版本的 goto,用來改變迴圈的行進,差別如下:

    • continue:重新開始迴圈
    • break:跳出迴圈

    由於有 continuebreak,我們現在很少需要撰寫 goto 語句。

    以下是實例:

    #include <stdbool.h>
    #include <stdio.h>
    
    int main()
    {
        int num;
        char b[256];
        // Run infinitely until the user input a valid number.
        while(true) {
            // Prompt for user input.
            printf("Input a number: ");
    
            if (scanf("%d", &num) != 1) {
                // Trick to prevent infinite loop.
                scanf("%s", b);
    
                // Show error message to the user.
                fprintf(stderr, "Invalid input\n");
    
                continue;  // Re-start the loop.
            }
    
            // Show info message to the user.
            printf("You input %d\n", num);
    
            break;  // Exit the loop.
        }
    
        return 0;
    }

    goto

    goto 是一種短程跳躍的語法,在同函式內可前往任意的位置。結構化的程式設計 (structured programming) 會盡量避免使用 goto,因過度使用 goto 會使得程式難以維護。不過,goto 偶爾可以使語法更簡潔,像是在程式運行結束或中止後釋放系統資源。我們會於後續相關章節會展示其用法。

    return

    return 用於跳出函式敘述並回到函式呼叫所在的位置,若是跳出主函式 (main) 則結束程式,若函式中沒有使用 return 則會執行完所有的函式敘述後自動跳出函式。詳見後文有關函式的介紹。

    【分享本文】
    Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email
    【追蹤新文章】
    Facebook Twitter Plurk