預設情形下,程式執行的順序是由上至下,但我們可以透過控制結構 (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 if
和 else
都是選擇性的。else if
可以多個,而 else
僅有一個,且需放最後。這用邏輯思考來想即可,不要硬背。
由於 C 沒有強制空格,else if
和 else
可以換行,這種方式很適合搭配註解一起使用:
// 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 引導使用者輸入整數。但我們不能一廂情願地認定輸入的資料格式正確,仍要檢查使用者輸入的內容。以本例來說,若輸入的格式正確,我們就檢查該數字是奇數或偶數;若不正確,則跳出錯誤訊息。
(選擇性) 排列 else if
和 else
敘述的方式
許多 C 教學書會使用以下方式來排列 else if
敘述:
if (a) {
/* statement_a. */
} else if (b) {
/* statement_b. */
}
做為 C 家族語言,Golang 甚至把這種風格寫死在程式碼重排軟體中,沒得修改。
然而,筆者會建議用以下的方式排列程式碼:
if (a) {
/* statement_a. */
}
else if (b) {
/* statement_b. */
}
因為 if
和 else if
縮進在同一行上,做為一種視覺提示,閱讀程式碼時可立即知道這兩段敘述是同一層。在寫巢狀控制結構時,這樣的程式碼排列方式格外有用。
有些程式設計者甚至引入 Pascal 或 C# 的程式碼排列方式:
if (a)
{
/* statement_a. */
}
else if (b)
{
/* statement_b. */
}
但筆者沒有使用這樣的風格。
本節所建議的事項只是一種撰碼風格,不具強制性。讀者仍然可以使用自己喜歡的風格。
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
來進行判斷。讀者目前不用在糾結在時間物件的使用細節,目前就當固定用法即可,學到指標後自然會這種寫法。
switch
和 if
是相通的,讀者可試著用 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)
自然。
continue
和 break
這兩種語法算是限縮版本的 goto
,用來改變迴圈的行進,差別如下:
continue
:重新開始迴圈break
:跳出迴圈
由於有 continue
和 break
,我們現在很少需要撰寫 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
則會執行完所有的函式敘述後自動跳出函式。詳見後文有關函式的介紹。