Loops
while loop
範例輸出: 這個程式會不斷要求使用者輸入整數, 然後當使用者輸入的資料不是數字時, 迴圈就會結束,並且把所有數目的總 和算出來。 範例2-1 #include <stdio.h> int main(void) { long num; long sum = 0L; int status; printf("Please enter an integer to be summed "); printf("(q to quit): "); status = scanf("%ld", &num); /* %ld for long, status is the return value */ while (status == 1) { /* == means "is equal to" */ sum = sum + num; printf("Please enter next integer (q to quit): "); status = scanf("%ld", &num); } printf("Those integers sum to %ld.\n", sum); return 0; } 這個程式會不斷要求使用者輸入整數, 然後當使用者輸入的資料不是數字時, 迴圈就會結束,並且把所有數目的總 和算出來。 Please enter an integer to be summed (q to quit): 12 Please enter next integer (q to quit): 34 Please enter next integer (q to quit): -56 Please enter next integer (q to quit): 789 Please enter next integer (q to quit): q Those integers sum to 779. 範例輸出:
範例2-1 #include <stdio.h> int main(void) { long num; long sum = 0L; int status; printf("Please enter an integer to be summed "); printf("(q to quit): "); status = scanf("%ld", &num); /* %ld for long, status is the return value */ while (status == 1) { /* == means "is equal to" */ sum = sum + num; printf("Please enter next integer (q to quit): "); status = scanf("%ld", &num); } printf("Those integers sum to %ld.\n", sum); return 0; } 宣告為 long 的變數可以儲存更大範圍的整數值。 因為要讀的是 long,所以用 %ld。另外有個新東 西是我們用到了 scanf() 的回傳值。其實每次呼叫 scanf() ,它除了把輸入的資料存入參數之外,還 會回傳一個整數值給呼叫者,回傳的是它成功讀取 的資料數目。 以這個例子來說,如果 scanf() 能成功讀到 1 個長整 數 (對應到 %ld),它就會回傳 1,如果使用者輸入的 不是整數 (譬如輸入英文字母 'q'),則 scanf() 會回 傳 0 表示沒有讀到任何整數。
#include <stdio.h> int main(void) { long num; long sum = 0L; int status; printf("Please enter an integer to be summed "); printf("(q to quit): "); status = scanf("%ld", &num); /* %ld for long, status is the return value */ while (status == 1) { /* == means "is equal to" */ sum = sum + num; printf("Please enter next integer (q to quit): "); status = scanf("%ld", &num); } printf("Those integers sum to %ld.\n", sum); return 0; } 範例2-1 當進入 while 迴圈的時候,會先判斷 status 是否 等於 1,等於 1 才表示 scanf() 有讀到整數資料。 在 C 語言裡判斷兩個數是否相等要用 == 符號, 也就是兩個等號連在一起。 注意!千萬不要錯用成一個等號,因為 = 的意義是 assignment,也就是設定變數值,剛開始學 C 語言 最常犯的錯誤之一就是把 == 寫成 =,大多數時候 如果有這樣的誤用,程式 compile 還是會通過,而 且也可以執行,但是意義完全不同,所以得到的結 果會是錯的。
邏輯運算跟位元運算 &&與&、││與│的差別? 邏輯運算子:&&、││ 運算結果只會有1跟0兩種情況,做為判斷True or False。 例如:while(i > 10 && i < 20),這是當10<i<20的時候 會繼續執行while迴圈,反之則跳出迴圈。 位元運算子:&、│ 位元運算則是將運算子兩邊的運算元的每個bit做運算。 例如:a = 7 & 2,則a = 2。 邏輯運算子大部份應用在需要做判斷的情況,例如迴圈的判斷, 而位元運算子則大部份做為一般的計算。更多的應用將會往後 的例子中提到。 注意不要將位元運算的&與scanf用到的&搞混。
C-style loop 前面範例的迴圈,在寫法上通常會被簡化。譬如,原來是 會習慣寫成 這樣的寫法可以省去變數 status。這樣的用法等於一次做兩 個動作,先利用 scanf()讀入資料,同時判斷回傳值是否等於 1。熟練之後,這樣的寫法會蠻簡潔方便。如果還不熟練,寫 的時候就要稍微小心不要造成迴圈停不下來。 status = scanf("%ld", &num); while (status == 1) { sum = sum + num; printf("Please enter next integer (q to quit): "); status = scanf("%ld", &num); } while (scanf("%ld", &num) == 1) { sum = sum + num; printf("Please enter next integer (q to quit): "); }
注意停止條件 使用 while 迴圈要注意停止條件,看看底下這個錯誤示範 執行這個程式會發生什麼事? 迴圈裡面完全沒有更改 i 的值, 所以 i < 5 的條件會一直成立,迴圈無法停下來,只能用暴 力手段把程式停掉。而如果程式改成 程式還是錯的,但是有可能會自己停止。當 i 不斷遞減變成 絕對值越來越大的負數,到了極限之後反而變成絕對值最大 的正數,等於繞了一圈循環回來,所以 i 的值就比 5 大,迴 圈就停止了。下頁的程式碼可以看出循環的現象。 int i = 1; while (i < 5) { printf("Hello!\n"); } int i = 1; while (--i < 5) { printf("Hello!\n"); }
注意停止條件 上面的三個範例可看出循環的現象。 由於 char 型別只佔一個 byte 所以可以比較快停下來。 如果換成 int 就要跑比較久。 範例2-2 範例2-3 範例2-4 #include <stdio.h> int main(void) { char i = 1; while (--i < 1) { printf("%d\n", i); } printf("%d\n", i); return 0; } #include <stdio.h> int main(void) { char i = 1; while (++i > 0) { printf("%d\n", i); } printf("%d\n", i); return 0; } #include <stdio.h> int main(void) { unsigned char i = 1; while (++i > 0) { printf("%d\n", i); } printf("%d\n", i); return 0; }
我們來看看如果是上面的範例 (把範例 2-1 更改了一個小地 方),執行結果會如何。 #include <stdio.h> int main(void) { long num; long sum = 0L; int status; printf("Please enter an integer to be summed "); printf("(q to quit): "); status = scanf("%ld", &num); /* %ld for long, status is the return value */ while (status = 1) { /* == means "is equal to" */ sum = sum + num; printf("Please enter next integer (q to quit): "); status = scanf("%ld", &num); } printf("Those integers sum to %ld.\n", sum); return 0; } 我們來看看如果是上面的範例 (把範例 2-1 更改了一個小地 方),執行結果會如何。 當我們輸入整數時,程式看起來還正常,但是當我們輸入 ‘q’則程式就掉進了無止境的迴圈,自己不停的印出 printf() 裡的資訊。
總之,在寫停止條件時要特別小心,尤其是 == 和 = 不要錯用。 #include <stdio.h> int main(void) { long num; long sum = 0L; int status; printf("Please enter an integer to be summed "); printf("(q to quit): "); status = scanf("%ld", &num); /* %ld for long, status is the return value */ while (status = 1) { /* == means "is equal to" */ sum = sum + num; printf("Please enter next integer (q to quit): "); status = scanf("%ld", &num); } printf("Those integers sum to %ld.\n", sum); return 0; } 這個 while 迴圈跟 while(1) 沒兩樣,會不斷要求輸入整數。當你在 scanf() 的地方輸入‘q’ 的時候,雖然 scanf() 會回傳 0 給 status,但是到了 while (status = 1) 的地方 status 又被設成 1。 而為什麼程式會開始不再等待使用者輸入一直做printf,這就和 scanf()有關。 當 scanf() 讀不到要讀的東西(此範例為long)它會把讀到但格式不符的資料 丟回 buffer,暫時保留在那裡下次再處理。所以下次再呼叫 scanf() 的時候 ‘q’ 依舊在那裡,而依舊回傳 0。 總之,在寫停止條件時要特別小心,尤其是 == 和 = 不要錯用。
32 bit in 32-bit OS 32 bit in 64-bit Windows and Mac 註:變數size大小 char 8 bit = 1 byte int 16 bit in 16-bit OS 32 bit in 32-bit OS 32 bit in 64-bit OS long 32 bit in 32-bit OS 32 bit in 64-bit Windows and Mac 64 bit in 64-bit Linux long long 64 bit short 16 bit in 32-bit OS compiler(編譯器)也會決定type(型別)的size大小
迴圈何時停止 輸出: n = 1 Now n = 2 n = 2 Now n = 3 The loop has finished. 範例2-5 #include <stdio.h> int main(void) { int n = 1; while (n < 3) { printf("n = %d\n", n); n++; printf("Now n = %d\n", n); } printf("The loop has finished.\n"); return 0; } 輸出: n = 1 Now n = 2 n = 2 Now n = 3 The loop has finished. 當程式第二次執行到 n++; 之後,n 的值變成 3,雖然已經 不再滿足迴圈預期的條件,但是迴圈並不會在這個時候立刻 停止,要等到執行完接下來的 printf("Now n = %d\n", n); 然後回到 while 的開頭,再次判斷 (n < 3) 時,條件不成立, 才會整個跳出被 { } 所包含的 while 區域。
條件式判斷符號 總共有六種:小於 < 小於等於 <= 相等 == 大 於等於 >= 大於 > 不相等 !=。在比較兩個數 值的大小關係時,如果比較的是浮點數(float、 double),要特別注意,只能使用 > 和 <,因為 經過一些數學運算,兩個浮點數是否還能完全相 等往往會出乎我們意料之外。譬如 基本迴圈是停不下來的,照理說只要執行六次迴 圈 a 的值就會累加到 1.0,但是由於是以數值來儲 存 1.0/6.0,所以會有誤差,最後使得 a 不能精確 地等於 1.0。 範例2-6 double a = 0; while (a != 1.0) { a = a + 1.0/6.0; }
當使用者輸入的值和 3.14159 的差異超過 0.0001,迴圈就會繼續要求 使用者輸入,直到誤差小於 0.0001 才能結束迴圈。 既然使用浮點數無法精確得到相等的數值,我們就只能用近似的方式, 當兩個數值的差異小於某個可容忍範圍,就應該接受,讓迴圈停止。在 math.h 檔裡有宣告一個叫做 fabs() 的 function,可以用來計算浮點數 的絕對值。我們用它來算兩個浮點數相減的絕對值。 當使用者輸入的值和 3.14159 的差異超過 0.0001,迴圈就會繼續要求 使用者輸入,直到誤差小於 0.0001 才能結束迴圈。 範例2-7 #include <math.h> #include <stdio.h> #define ANSWER 3.14159 int main(void) { double response; printf("What is the value of pi?\n"); scanf("%lf", &response); while (fabs(response - ANSWER) > 0.0001) { printf("Try again!\n"); scanf("%lf", &response); } printf("Close enough!\n"); return 0; }
範例2-8 #include <stdio.h> int main(void) { int true_val, false_val; true_val = (10 > 2); false_val = (10 == 2); printf("true = %d; false = %d \n", true_val, false_val); return 0; } 輸出: true = 1; false = 0 這個程式的目的是要觀察 true 和 false 對應的整數值到底是多少。 描述兩個數值關係的 expression 如果成立 (true),則可以用整數 值 1 來表示,如果關係不成立 (false) 值就是 0。所以在 C 程式裡 我們可以用 1 來代表 true 而用 0 來代表 false。有時候程式為了 製造無窮迴圈,會用下面的寫法: 其實在 C 程式裡不只 1 可以代表 true,任何非零的數都代表 true, 但是只有 0 代表 false。看看下一頁的範例 while (1) { ... }
輸出: 只要 while 迴圈繼續,就表示 n 的值相 當於 true。 3 is true 2 is true 1 is true 範例2-9 輸出: #include <stdio.h> int main(void) { int n = 3; while (n) { printf("%2d is true\n", n--); } printf("%2d is false\n", n); return 0; } 3 is true 2 is true 1 is true 0 is false 只要 while 迴圈繼續,就表示 n 的值相 當於 true。
for-loop 假如我們一開始就知道迴圈將會執行多少 次,可以使用 for 迴圈來達到反覆計算的 效果,使用上會比較方便。最標準的狀況 就是使用一個 counter 來計算迴圈執行次 數,當 counter 達到預定的次數迴圈就停 止。
輸出: 範例2-10 #include <stdio.h> int main(void) { int num; int i; printf("Enter an integer: "); scanf("%d", &num); for (i = 0; i < num; i++) { printf("$"); } printf("\n"); return 0; } Enter an integer: 30 $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ 由於已經用 scanf() 讓使用者輸入迴圈執 行的次數,所以我們知道迴圈將會執行 num 所代表的次數。假設我們用變數 i 當 作 counter,變數 num 是要反覆執行的 次數,則 for 迴圈的語法就是 這個語法包含三個部份,用兩個分號隔開 成三個區域,第一個部份用來設定 counter 的初值。第二個部份判斷是否應 該繼續執行。第三個部分可用來更改 counter 的值,每跑回一次迴圈就會再被 執行一次。 for (i = 0; i < num; i++) { ... }
輸出: 輸出: 底下這些範例做的事情都很容易理解,主要是讓大家熟悉 for 的語法。 #include <stdio.h> 範例2-11 輸出: #include <stdio.h> int main() { int n; for (n = 2; n < 60; n = n + 13) { printf("%d \n", n); } return 0; } 範例2-12 #include <stdio.h> int main(void) { char ch; for (ch = 'a'; ch <= 'z'; ch++) { printf("The ASCII value for %c is %d.\n", ch, ch); } return 0; } 輸出:
其中三個區域的每個區域所做的事情 還可稍加變化。譬如如果什麼都不做, 寫成 所以 for 迴圈的語法就是 其中三個區域的每個區域所做的事情 還可稍加變化。譬如如果什麼都不做, 寫成 作用則相當於無窮迴圈。 for ( 初始化 initialize ; 是否繼續執行的條件判斷 test ; 更新變數值 update ) { ... } for ( ; ; ) { printf("Do something.\n"); }
底下的範例是另一種for迴圈的無窮迴圈寫法: 這個程式會不斷讓使用者輸入數字,直到使用者猜到密 碼是 7 為止。在初始化的地方偷渡了一個 printf() 的動 作,所以剛進入迴圈時會顯示一次 "Keep entering numbers!"。第二部份條件判斷檢查是否輸入的數字等 於預設密碼。第三部份則沒有東西,因為 num 靠迴圈 裡的 scanf() 來做更新的動作。 範例2-13 #include <stdio.h> #define CODE 7 int main(void) { int num = 0; for (printf("Keep entering numbers!\n"); num != CODE; ) { scanf("%d", &num); } printf("Bingo!\n"); return 0; } 輸出: Keep entering numbers! 1 6 3 7 Bingo!
其他 Assignment 符號: +=,-=,*=,/=,%= 這些可看成是簡寫的 assignment 符號, x += 5; 相 當於 x = x + 5; 而 y %= 7; 相當於 y = y % 7; 以此 類推。在 for 迴圈的第三部份 (更新變數值) 用這樣 的寫法會比較簡潔,但你不一定要用這樣的寫法, 只要順著自己的習慣就可以。 在 for 迴圈的第一部份 (初始化) 和第三部份 (更新變 數值),其實允許我們做兩個以上的 statements, 詳見下頁範例。
範例2-14 輸出: #include <stdio.h> #define FIRST_PACK 7 #define NEXT_PACK 5 int main(void) { int n, cost; printf(" packs costs\n"); for (n=1, cost=FIRST_PACK; n<=10; n++, cost+=NEXT_PACK) { printf("%5d $%-5d\n", n, cost); } return 0; } packs costs 1 $7 2 $12 3 $17 4 $22 5 $27 6 $32 7 $37 8 $42 9 $47 10 $52 在初始化的部份做了兩個動作,兩個動作用逗號分開,分 別是設定 n=1 以及設定 cost=FIRST_PACK。更新變數值 的部份也做了兩個動作,先做 n++ 接著做 cost+=NEXT_PACK,這樣就能讓變數 n 的值被加一 (負責 累計迴圈反覆次數),而且也累計 cost 值。在這裡用逗號隔 開的兩個動作是以循序方式執行,也就是做完第一個動作 才做第二個。
使用 do ... while 迴圈 某些情況下,迴圈會保證至少被執行一次,這時候就適合用 do ... while 迴圈,它的效果是先做迴圈內容,最後在判斷條 件是否要繼續,譬如下面的例子,要求使用者要輸入密碼, 直到輸入正確為止。要注意while 的條件判斷之後有個分號。 範例2-15 #include <stdio.h> #define CODE 13 int main(void) { int code_entered; do { printf("Please enter the secret code number: "); scanf("%d", &code_entered); } while (code_entered != CODE); printf("Bingo!\n"); return 0; }
這是 do. while 和標準 while 的語法不同之處。 可以比較看看,如果用 while 而不是 do 這是 do ... while 和標準 while 的語法不同之處。 可以比較看看,如果用 while 而不是 do ... while, 寫起來會有點累贅: 範例2-16 #include <stdio.h> #define CODE 13 int main(void) { int code_entered; printf("Please enter the secret code number: "); scanf("%d", &code_entered); while (code_entered != CODE) { printf("Please enter the secret code number: "); scanf("%d", &code_entered); } printf("Bingo!\n"); return 0; }
Nested Loops 多重迴圈 更複雜的迴圈使用方法是用一個迴圈包住另一層迴圈。使用 雙重迴圈最常用來處理以二維或表格方式呈現的資料。譬如 我們想要輸出一個用 * 號填滿的長方形,寬和高分別是 25 和 7。我們已經學過用一層迴圈反覆輸出,所以要印出一排 25 個星號可以用下面的寫法 顯示了一串 25 個 * 之後,再用 printf("\n"); 換行。接下來如 果我們再用一個迴圈把上面的程式碼包起來,變成 就會重複做 7 次,而每次都會顯示一排 25 個 * 號。 for (j = 0; j < 25; j++) { printf("*"); } printf("\n"); 範例2-17 for(i = 0; i < 7; i++) { for (j = 0; j < 25; j++) { printf("*"); } printf("\n"); } 輸出:
輸出: 外層迴圈負責反覆六次印出六行字串,內層 迴圈則負責在每一行顯示連續的英文字母, 每一行顯示的字母範圍都不一樣。 ABCDEFGHIJ 範例2-18 #include <stdio.h> int main(void) { int row; char ch; for(row = 0; row < 6; row++) { for (ch = ('A' + row); ch < ('A' + 10); ch++) { printf("%c", ch); } printf("\n"); } return 0; } 輸出: ABCDEFGHIJ BCDEFGHIJ CDEFGHIJ DEFGHIJ EFGHIJ FGHIJ 外層迴圈負責反覆六次印出六行字串,內層 迴圈則負責在每一行顯示連續的英文字母, 每一行顯示的字母範圍都不一樣。
陣列 (Arrays) 使用方法簡介 我們在後面的課程會詳細介紹陣列。但是這裡我們先簡介陣 列的使用方法。陣列就是一連串相同型別的資料存放在連續 的空間中,整個陣列的內容只需要用一個名字來統稱,而其 中每個元素可以藉由索引 index 方式取出。宣告陣列的方法 如下 這樣就表示 nums 是一個陣列,包含 20 個元素,每個元素 可用來記錄一個型別為 float 的數值。陣列的第一個元素叫 做 nums[0],第二個元素是 nums[1],以此類推,最後一個 元素是 nums[19]。所以 C 的陣列編號方式是從 0 開始編號 而不是從 1 開始,這一點要特別注意。每個元素可以當作一 個變數來使用,譬如 或是 float nums[20]; nums[3] = 3.4; nums[9] = 18.5; scanf("%f", &nums[4]); /* 讀入一個小數存放在第五個元素裡 */
程式 compile 之後,執行到像是上面那兩行的時候,程式 非常可能就會當掉,因為它試圖去讀取錯誤的記憶體位置中 的東西。 由於 C 並不會去檢查我們給的 index 是否超出當初宣告的陣 列大小範圍,所以可能會寫出下面有 bug 的程式而沒有察 覺,compiler 也不會跟我們說程式有 error 程式 compile 之後,執行到像是上面那兩行的時候,程式 非常可能就會當掉,因為它試圖去讀取錯誤的記憶體位置中 的東西。 我們還可以宣告其他類型的陣列,譬如: 這裡順便複習一下,字元陣列和字串的差別只在最後是否有 '\0' 結束符號,如果要當作字串來使用,字元陣列要加入 '\0' 當作結束字元。不同型別的陣列佔用的記憶體空間也不 同,例如 int 陣列每個元素佔用四個 bytes,而 char 陣列每 個元素只佔一個 byte。 nums[20] = 2.5; /* A BUG */ nums[30] = 3.6; /* A BUG */ a[2] a[1] a[0] int a[3]; long long c[3]; char b[3]; c[1] c[0] b[2] b[1] b[0]
字元陣列 character array 與字串 字串裡的字元必須連續地存放在記憶體中,所以剛好可以用 陣列來儲存,因為陣列就是一連串的記憶體空間 字元陣列的每一格空間可以存放一個字元 (char) 當我們宣告 char name[10]; 表示要保留十格空間存放十個 字元,每一格可以容納一個 char 型別的資料 為了標記整個字串究竟在哪裡算是結尾,C 語言使用一個特 殊的字元 '\0' 來表示字串結尾。字元 '\0' 對應到的 ASCII 值 是 0。我們也可以用整數 0 來代替字元 '\0',但為了有所區 別,當字元使用時最好寫成 '\0' 宣告一個字元變數和宣告一個陣列的差別可以用下圖來表示 char ch; char name[10];
宣告陣列產生一個可以容納十個字元的 array,準 備用來記錄使用者輸入的,因為要保留一格給 ‘\0’ 字元來標示字串結尾,所以其實真正能用 來記錄字串的長度,最多只能包含九個字元。 如何把字串存入陣列中呢? 最簡單的方法是 scanf("%s", name); 讀取使用者輸入的字串。所以 %s 就表示要把使 用者輸入的東西當作 "字串" 讀進來,然後參數 name 就是要存放字串的陣列名稱,這個名稱所 代表的意義是整個字串的開頭位址。因此 scanf() 就能由 name 找到陣列開頭位址,一格一格把字 元填進去,而且會自動在最後加上 '\0' 當作結束。 字元陣列和字串陣列的差別只在最後是否有 '\0' 結束符號。不同型別的陣列佔用的記憶體空間也 不同,例如 int 陣列每個元素佔用四個 bytes,而 char 陣列每個元素只佔一個 byte。
搭配陣列來使用迴圈 範例2-19 #include <stdio.h> int main(void) { int i; float hours[7], average; printf("Enter the hours of sleep per night last week\n"); for(i = 0; i < 7; i++) { scanf("%f", &hours[i]); } printf("The numbers you entered are as follows:\n"); printf(" %4.1f", hours[i]); printf("\n"); for(i = 0, average = 0; i < 7; i++) { average += hours[i]; average /= 7; printf("Your average hours of sleep per night were %.1f hours.\n", average); return 0; } Enter the hours of sleep per night last week 9 10 4 12 13 14 3.5 The numbers you entered are as follows: 9.0 10.0 4.0 12.0 13.0 14.0 3.5 Your average hours of sleep per night were 9.4 hours. 輸出: 此範例先用迴圈讓使用者輸入 七個數字存在陣列裡,然後再 用迴圈一一把陣列裡的元素再 顯示出來,然後再用迴圈把元 素的值累加起來,最後計算出 平均值。 有幾個要注意的地方,第一是 迴圈裡的用來當陣列 index 的變數要從 0 開始;第二是 用來累加總和並計算平均的變 數 average 要先設定初值等 於 0;第三是 scanf() 裡陣列 元素的寫法,當我們要把值存 入某個陣列元素時,別忘了加 & 符號。
使用for or while 在一開始使用迴圈時,一定會對於要用哪 一種迴圈產生疑惑,以下對for與while做一 個分析。
Appendix
for的特殊語法 也可以寫為 當參數i只在此for迴圈使用的時候,下面的寫法會讓compiler將記憶體空間做較好的分配。 int i; for(i = 0; i < 10; i++) { … } 也可以寫為 for(int i = 0; i < 10; i++) { … } 當參數i只在此for迴圈使用的時候,下面的寫法會讓compiler將記憶體空間做較好的分配。 不過這僅限於C99以後的標準。