創作內容

74 GP

[達人專欄] 跟著豬腳 C 起來:利用迴圈重複動作,省時又省力

作者:解凍豬腳│2019-07-13 20:16:12│贊助:148│人氣:2398
 

  大家晚安。還記得上次提到了如何使用陣列或結構(甚至兩者搭配起來)來宣告大量的變數,把它們分別編號化或模擬成一個物件。

  然而宣告歸宣告,如果只憑靠先前豬腳分享的這些技巧,那仍然應付不了大量處理的需求,自然就無法發揮電腦的優勢了,不是嗎?

  所以,今天就來談談程式的精髓「迴圈」吧。當我們在開發程式的時候,總是會遇上需要重複執行類似行為的時候,例如我們想要求 2 的 0 次方到 30 次方的值(並且顯示出來),那我們可以這麼做:

    int result = 1;
    printf("2 的 0 次方為 %d\n", result);
    result *= 2;
    printf("2 的 1 次方為 %d\n", result);
    result *= 2;
    //(這段中間我就省略了,因為都是重複的動作)
    printf("2 的 29 次方為 %d\n", result);
    result *= 2;
    printf("2 的 30 次方為 %d\n", result);

  算一算,包含宣告變數,單就這樣從 0 到 30 次方的計算,這樣加起來就會需要用到總共 62 行的程式碼,那不是很麻煩嗎?這時候我們有個新的做法:

    int result = 1;
    for (int i=0; i<=30; i++) {
        printf("2 的 %d 次方為 %d\n", i, result);
        result *= 2;
    }

  然後把它拿來執行:


  天啊豬腳,這真是太神奇了!我們居然只用了 5 行程式碼就可以做到剛才花了 62 行程式碼才能做到的事情。

  沒有錯,上述舉例的東西就是所謂的迴圈了。在 C 語言當中,我們有三種迴圈的形式可以使用:

  1. for 迴圈
  2. while 迴圈
  3. do-while 迴圈

  剛才的例子,相信大家一看就可以猜得到是上列的 for 迴圈了。

  一般來說,我們把 for 迴圈的設定(也就是小括號那段)依序分成三個部分:

  1. 初始行為
  2. 執行條件
  3. 單次迴圈結束行為

  回過頭來看剛剛的迴圈:

    for (int i=0; i<=30; i++) { ... }

  括號當中的意思依序是:

  1. 先宣告一個變數 i(並且設定初始值為 0)
  2. 設定迴圈的執行條件:「只要 i 小於或等於 30,那我就執行大括號裡面的行為」
  3. 每做完一次大括號裡的行為以後,所需要做的事情

  看起來好像很複雜,但其實我們可以把 1 和 2 擺在一起看,簡單來說就是令 i = 0 到 30,而其中驅動 i 往上加的,就是 i++ 這個東西了。

  先前我們曾經說過,i++ 可以用來代替 i += 1 這件事情,它「同時」也可以代表 i 這個數,也就是說它具備了數字的本質,也具備了「行為」。當然在這邊你可以把 i++ 改為 i += 1,但大部分的人習慣上還是用 i++,畢竟方便又漂亮。

  你還可以用「++i」來做到一樣的事,但它同時表示的是「i+1」這個數而不是「i」,不信你用 int i = 7; 和 printf("%d", ++i); 試試看就知道了。

  順帶補充一點,將 int i=0 寫在 for 迴圈裡的行為,可能會因為開發環境的設定不同而無法成功編譯,遇到這種情況時我們可以改成:

    int i;
    for (i=0; i<=30; i++) { ... }

  如果有遇到編譯問題的時候還請自己更改。我個人習慣是把宣告變數(int i=0)寫在 for 迴圈的括號裡,因此以下我仍然會把宣告變數跟 for 迴圈寫在一起。

  咳,回正題,也就是說整個迴圈的流程是這樣:

  1. 宣告變數 i,並且令它為 0
  2. 判斷 i 是否小於或等於 30,如果是的話就執行 { ... } 裡面的東西
  3. 令 i 往上加一
  4. 重複 2 跟 3 的動作,直到條件不符合就停

  你也可以把它倒過來做:

    int result = 1073741824;
    for (int i=30; i>=0; i--) {
        printf("2 的 %d 次方為 %d\n", i, result);
        result /= 2;
    }

  這樣的話,就會從 30 次方開始往下輸出,最後 i 會被扣到 -1 而令迴圈的循環結束。

  當然,如果單純想要計算次方的話,可以不需要像上例這麼麻煩得自己用變數儲存結果,如果我們在開頭使用 #include <math.h> 將 C 語言裡面自帶的數學相關函式標頭檔嵌進來,就可以把上面整段改成:

    for (int i=30; i>=0; i--) {
        printf("2 的 %d 次方為 %d\n", i, (int)pow(2, i));
    }

  這樣只需要三行就可以解決了。可以注意到這裡比較特別的是 pow(2, i):在 C 語言的 math.h 裡面,定義了 pow(x, y) 函數,表示 x 的 y 次方。然而,math.h 將這個函數的計算結果定義為 double 資料型態,所以如果你想要直接把它塞進平時給 int 用的 %d,就會顯示不出東西(除非你換成 %lf,但會有小數點),也因為如此,我們才會需要在這前面加上 (int) 來把結果轉換成 int。

  當然,for 迴圈沒有規定每次都只能 +1,如果你需要的話,也可以跳著做,這就看你如何去操控這個迴圈計數器(也就是 i,你想把它取別的名字也可以):

    for (int i=0; i<=30; i+=2) {
        printf("2 的 %d 次方為 %d\n", i, (int)pow(2, i));
    }

  結果就會是這樣:


  總之呢,for 迴圈可以很直觀地設定起點跟終點(而且這個 i 變數的使用範圍會被限定在該迴圈裡,不會跟外面的同名變數衝突),還能設定方向(增或減)和速度(每次增減數量),是三種迴圈裡面最基本也最泛用的。

  另外一種則是「while」迴圈,它沒有硬性規定必須使用像上面 for 迴圈所用到的計數器,而是單純用「條件」來判斷。例如今天有個情境:「假設豬腳的存款有 18,000 元,銀行每個月都會給予 0.08% 的利息(複利),那麼豬腳要經過幾個月,存款總額才會達到 19,000 元呢?」

  我們這樣做:

    int monthCount = 0;
    double money = 18000;
    while (money <= 19000) {
        money *= 1.0008;
        monthCount++;
    }
    printf("經過了 %d 個月, 豬腳的存款終於來到了 %lf", monthCount, money);

  首先把初始值設定好(也就是豬腳原有的存款,以及用來儲存「經過了幾個月」的計數器),接著就直接開始執行迴圈:

  1. 只要豬腳的存款還沒達到 19,000
  2. 把它下個月的存款算出來,儲存結果
  3. 因為已經過了一個月,所以把計數器往上 +1

  重複上面三個步驟,直到存款達 19,000 為止,這樣我們就可以知道豬腳在每月複利計息 0.8% 的情況下,需要花 68 個月才能夠從 18,000 變成 19,000 了。

  實務上,while 迴圈通常都是用來達成「由外部條件來決定的持續行為」,又或者是用來做「持續不停的行為」,例如你想做個「每秒跳一次的計時器」,就可以這麼做:

#include <stdio.h>
#include <windows.h>

int main(void) {
    while (1) {
        Sleep(1000); // 請注意這個是 windows.h 裡面才有的東西
        printf("滴答\n");
    }
}

  這裡就要談到條件式的問題了:為什麼這裡要填 1 呢?

  實際上,我們平常在 C 語言裡面做 if (...) {...}、while (...) {...}、for (...) {...} 的時候,當我們要做條件判斷(例如上面銀行利息的例子當中「money <= 19000」這個地方),這個填寫條件的位置,最後都是會計算出一個「結果」的。

  在電腦眼中,這麼複雜的東西它們做不來,所以會逐步把式子化簡,然後才變成它們可以直接處理的東西。例如,在 67 個月過去以後,在電腦眼中,這個 while 迴圈的條件式就會依序這樣變化:

  1. while (money <= 19000)
  (讀取 money 變數儲存的資料)
  2. while (18990.717921 <= 19000)
  (因為 18990.717921 <= 19000 成立,所以是 1)
  3. while (1)
  4. 因為 while 裡面的結果是 1,所以執行 {...} 裡面的東西

  到了第 68 個月的利息結算完以後,則是:

  1. while (money <= 19000)
  (讀取 money 變數儲存的資料)
  2. while (19005.910495 <= 19000)
  (因為 19005.910495 <= 19000 不成立,所以是 0)
  3. while (0)
  4. 因為 while 裡面的結果是 0,所以不執行 {...} 裡面的東西,跳出迴圈

  所以,在上面的計時器中,可以用常數 1 來讓迴圈強制重複執行,直到你把程式關閉為止。

  畢竟,1 永遠是 1,就跟你交不到女朋友的命運一樣,永遠改變不了。


  這種逐步簡化的概念,正是所謂的「布林代數」(boolean)。所以我們時常說「電腦的眼中只有 1 和 0」,這個道理不只在先前「用電腦來操控資料吧」篇提到過,判斷條件也是如此。

  在 for/while 迴圈裡面,如果我們想要有計畫地跳過某些特定的數字,可以使用 continue 的句法,跳過該次的迴圈:

    for (int i=0; i<=20; i++) {
        if (i%3 == 0) { // 如果 i 除以 3 得到的餘數是 0 的話
            continue;
        }
        printf("%d\n", i);
    }

  這樣做的話,當 i 是 3 的倍數而執行 continue 的時候,就會自動結束這一輪的迴圈,也就是略過 for 迴圈裡面、continue 以下的所有工作:


  另外一種情況是,你想要完全結束整個迴圈,就使用 break,這樣就會直接跳到迴圈外面了:

    for (int i=1; i<=20; i++) {
        if (i%7 == 0){
            break;
        }
        printf("%d\n", i);
    }


  多數情況下,我們可以用 for 迴圈和 while 迴圈就達到大部分重複性質的工作,這兩種迴圈是最主要的方式。

  最後要談的一種,則是 do-while 迴圈。它的作用跟 while 差不多,但它是「先執行過一次,之後才檢查條件決定是否要繼續」,也就是一定會執行「至少一次」迴圈:

    int i = 0;
    do {
        printf("%d\n", i);
        i++;
    } while (i <= 5);


  在上面的例子當中,只要 i 還沒達到 5 就再繼續做,這大概就是「去面試了一個工作先做做看,如果覺得不爽再辭職」的概念。

  學會使用 for、while 迴圈以後,當然就有了更深一層的問題:「如果我想要的是二維的循環呢?」

  比如說,你想要列出九九乘法表的每個結果,那怎麼辦?你單用一個 for 迴圈,當然不是做不到,但這前提必須建立在你早就知道答案:

    for (int i=1; i<=81; i++){
        if (i%9 != 0){
            printf("%d*%d=%d\t", i/9+1, i%9, (i/9+1)*(i%9));
        }else{
            printf("%d*%d=%d\t", i/9, 9, (i/9)*9);
            printf("\n");
        }
    }


  ——如果早就知道答案了,那我還要寫程式幹嘛?這不是本末倒置嗎?

  總之呢,別擔心!寫程式就像組積木一樣,迴圈都是可以組合起來的。我們可以在迴圈裡面使用迴圈:

    for (int i=1; i<=9; i++) {
        for (int j=1; j<=9; j++){
            printf("%d*%d=%d\t", i, j, i*j);
        }
        printf("\n");
    }


  我們用更簡單的方法也達到了一樣的目的。

  原理非常簡單,我們只是依序做了這樣的事:

  1. 在 i 為 1 的情況下,依序執行 j = 1 到 j = 9 的工作
  2. 換行(輸出換行符號 \n)
  3. 在 i 為 2 的情況下,依序執行 j = 1 到 j = 9 的工作
  4. 換行(輸出換行符號 \n)
  5. 在 i 為 3 的情況下,依序執行 j = 1 到 j = 9 的工作
  6. 換行(輸出換行符號 \n)
  ……
  15. 在 i 為 8 的情況下,依序執行 j = 1 到 j = 9 的工作
  16. 換行(輸出換行符號 \n)
  17. 在 i 為 9 的情況下,依序執行 j = 1 到 j = 9 的工作
  18. 換行(輸出換行符號 \n)


  也就是說,j 每執行完 9 輪,i 才會往上 +1,直到 i 的迴圈結束為止。

  你當然也可以利用 i 來當作內層迴圈起點的參考值:

    char str[256] = "ABCDE";
    for (int i=0; i<=4; i++) {
        for (int j=i+1; j<=4; j++) {
            printf("%c%c, ", str[i], str[j]);
        }
        printf("\n");
    }


  像上面的例子,就變成了典型的排列組合之中的「五選二組合」。

  總之呢,我們使用迴圈、條件判斷等有大括號的語法時,「縮排」的習慣就顯得重要了。只要你利用 space(或是 tab)把這些程式碼的從屬關係歸類好,這樣寫 code、修改 code 的時候,一看就可以知道原來 j 的 for 迴圈是在 i 的 for 迴圈裡面,輕易地看出每次執行完 9 輪 j 的迴圈以後,都會執行一次換行。

  不信你可以試一下沒有好好縮排的程式碼會變得多難看:

#include <stdio.h>

  int main(void) {
    for (int i=1; i<=9; i++) {
for (int j=1; j<=9; j++){
                printf("%d*%d=%d\t", i, j, i*j);
     }
printf("\n");
}  }

  有些程式語言,並不會強制使用縮排(例如我們現在使用的 C 語言),有的就會(例如 Python),因為 Python 語言是不使用大括號來表示從屬關係,而是直接用縮排來決定,所以只要你 Python 沒有縮排好,就可能造成程式邏輯整個錯誤。

  即使 C 語言沒有這樣的規定,我們仍然要養成寫 code 的時候一面寫、一面調整縮排的習慣,這樣當你以後回來修改自己的 code 時,才不會覺得懷疑人生(特別是程式碼很複雜的時候)。

  這樣的用法,我們通常叫做「巢狀迴圈」,也就是迴圈裡面又放了一個迴圈。不過,這種情況所造成的執行次數畢竟是以級數成長,所以要是你明明只是想解決一個小問題,你卻一連使用了好幾層迴圈(例如 for(m) { for(n) { for(p) { for(q) { ... }}}}),那你最好還是想個辦法優化你的程式邏輯,因為這樣的執行效率一定會很差。

  如果跟前篇提到的陣列搭配,更可以在迴圈裡面利用 array[i] 來一次控制整個 array 裡面的所有數,例如我想把陣列裡面每個數都乘以 2,我可以這麼做:

    int array[4] = {123, 456, 789, 444};
    
    printf("目前的 array 內容:\n");
    for(int i=0; i<4; i++){
        printf("%d: %d\n", i, array[i]);
    }

    for(int i=0; i<4; i++){
        array[i]*=2;
    }
    
    printf("乘以二之後的 array 內容:\n");
    for(int i=0; i<4; i++){
        printf("%d: %d\n", i, array[i]);
    }


  迴圈就是程式語言的精髓了。學會使用迴圈,你就掌握了電腦重複處理大量資料的方法。這玩意大概就是這樣了!我們下回見~
 
引用網址:https://home.gamer.com.tw/TrackBack.php?sn=4459060
All rights reserved. 版權所有,保留一切權利

相關創作

同標籤作品搜尋:第一次踏入墳場就上手|從入門到入墳|從入門到放棄|C語言|程式設計|C|C++|寫程式|程式|回收業者

留言共 30 篇留言

夜雨繁塵
ㄊㄓ

07-13 20:17

先不管這個
已經忘記前篇教什麼ㄌ

07-13 20:17

解凍豬腳
我七個月沒更新 C 語言系列了 ==07-13 20:25
我喜歡吃飯
不明覺厲

07-13 20:18

雨丸★(黑魂模式)
要學程式 數學的邏輯要夠強嗎?

07-13 20:19

解凍豬腳
數學不好一樣可以寫程式,但如果數學觀念好的話確實有加分07-13 21:10
Hokago
明厲

07-13 20:19

皮皮桑
(゚∀。)?

07-13 20:20

雞塊
假設for迴圈的判別條件已經有其他變數能用了 那能不宣告嗎

07-13 20:20

解凍豬腳
初始化的欄位可以留空:
int i=0;
for(;i<=30;i++){ ... }

順帶一提,這是死循環:
for(;;){}
基本上就相當於
while(1){}07-13 21:13
13

07-13 20:20

死神的助手
不能用loop迴圈嗎

07-13 20:24

解凍豬腳
其他語言才有「loop」這個關鍵字
例如 VB 就是用 Do ... Loop 07-13 21:15
卡加米
int second = 0;
double cut = 0;
while (cut <= 16 ) {
cut + = 1.6 ;
second ++;
}
printf("經過了 %d 秒, 桐人砍了 %lf"刀, second, cut);

07-13 20:48

解凍豬腳
1.「刀」要放在雙引號裡面
2. 迴圈條件不可以用 cut <= 16,因為剛好滿 16 的時候仍然滿足條件,所以還會再執行一次迴圈,導致結果變成 17.6
3.「+=」運算子是合在一起的,中間不可以加上空白
4. 真是勤學的好孩子,我覺得可以娶ㄌ07-13 21:26
Playlife
上面那邊2的次方倒回來是除不是乘 小錯誤

07-13 20:54

解凍豬腳
已修正
複製貼上的時候沒改到,感謝 XD07-13 20:58
落櫻飄雪
直接下拉 不想了解(¯―¯٥)

07-13 21:15

Eolico
該是時候遞迴一下ㄌㄅ

07-13 21:19

梧優
來朝聖ㄍ

07-13 21:20

Boycow
要是我高中程式語言學習社的社長有這ㄇ深入淺出,我可能就比較有興趣惹[e3]

07-13 21:34

young
認真推

07-13 22:00

MR4031(斷線中)
看不懂只能推個 https://truth.bahamut.com.tw/s01/201907/f0e5041069b4a44d76fe943772608f27.JPG

07-13 22:14

我不當邊芸人啦!JOJO
看不懂系列

07-13 22:26

熾心凝
學了python之後覺得c好麻煩= =

07-13 22:49

解凍豬腳
Python 真的會簡單到懷疑人生07-13 22:52
西瓜醬ฅˊ●ω●ˋฅ
窩學完全還給老師ㄌ

07-13 23:07

巴哈熊頭最後希望
九九乘法1%9!=0是判斷餘數還是實數r

07-13 23:13

解凍豬腳
那是 i 不是 1,運算符號 % 叫做 mod,是求餘數的意思
例如 20 % 7 = 6,至於 != 就是不等於的意思07-13 23:16
西瓜醬ฅˊ●ω●ˋฅ
#include <stdio.h>

int main(void)
{
float f, x, y, z;
for(y=1.5f; y>-1.5f; y-=0.1f)
{
for(x=-1.5f; x<1.5f; x+=0.05f)
{
z = x*x+y*y-1;
f = z*z*z-x*x*y*y*y;
putchar(f<=0.0f ? "johnny"[(int)(f*-8.0f)] : ' ');
}
printf("\n");
}

return 0;
}

07-13 23:53

解凍豬腳
原來西瓜醬是這樣看待我的……[e33]07-14 00:20
cftyn
補充一下,將變數宣告在for裡的寫法如:for (int i=0; i<=30; i++)
是C99以上的標準才支援
如果是用gcc的話預設標準應該會是gnu90,會編譯不過
gcc 編譯的時候加入
-std=c99或-std=gnu99或-std=c11或-std=gnu11
的參數就好了

07-14 01:26

解凍豬腳
感謝補充07-14 01:27
雪之王女‧F‧巧可奈
沒有相當的數理邏輯能力果然較難懂><

07-14 08:49

阿民
我還以為真的要變成有生之年系列了XD。豬腳最近很閒是嗎,居然有空寫教學,真的是太神奇了。Go只有for這東西

07-14 09:03

解凍豬腳
說閒也不盡然,之前是因為期末 + 偷懶,所以才會長達五個月沒出相關文章07-15 17:36
撕捏套取丟‘-ωก̀
這讓我想起之前高中資訊老師說重複做一樣的事是電腦最擅長的

07-14 13:41

解凍豬腳
以後等通用 AI 出來就不一樣了07-15 17:36
熾心凝
西瓜那樓沒設初值過不了編譯吧

07-14 19:45

福田まんじゅう
公蝦小==

07-14 23:58

敵會跪
交不到女朋友 可以交男.....

07-15 13:21

Misuki-三玖廚模式
基本的好像只剩if了 話說豬腳好像沒教到scanf(´・_・`)

07-19 15:32

我要留言提醒:您尚未登入,請先登入再留言

74喜歡★johnny860726 可決定是否刪除您的留言,請勿發表違反站規文字。

前一篇:[達人專欄] 論壇是怎麼... 後一篇:[達人專欄] 論壇是怎麼...

追蹤私訊

作品資料夾

gray0522小說
和表姊做愛看更多我要大聲說昨天21:57


face基於日前微軟官方表示 Internet Explorer 不再支援新的網路標準,可能無法使用新的應用程式來呈現網站內容,在瀏覽器支援度及網站安全性的雙重考量下,為了讓巴友們有更好的使用體驗,巴哈姆特即將於 2019年9月2日 停止支援 Internet Explorer 瀏覽器的頁面呈現和功能。
屆時建議您使用下述瀏覽器來瀏覽巴哈姆特:
。Google Chrome(推薦)
。Mozilla Firefox
。Microsoft Edge(Windows10以上的作業系統版本才可使用)

face我們了解您不想看到廣告的心情⋯ 若您願意支持巴哈姆特永續經營,請將 gamer.com.tw 加入廣告阻擋工具的白名單中,謝謝 !【教學】