重複結構以及迴圈練習

 

 

實 習 內 容




while 迴圈及 for 迴圈

1. 計次迴圈 (Counting loop)

2. 特定結束資料控制的迴圈 (Sentinel-controlled loop)

3. 什麼時候該用迴圈???

1

計次迴圈:

下面這個 while 迴圈程式片段由鍵盤讀入固定筆數的資料, 計算總薪資並列印出來:
count_emp = 0;           /* no employees processed yet */
while (count_emp < 7)    /* test value of count_emp    */
{
    printf("Hours> ");
    scanf("%d", &hours);
    printf("Rate> ");
    scanf("%lf", &rate);
    pay = hours * rate;
    printf("Pay is $%6.2f\n", pay);
    count_emp = count_emp + 1; /* increment count_emp	*/
}
printf("\nAll employees processed\n");

上面程式中哪一個變數是迴圈控制變數 (loop controlling variable)?

這個迴圈總共執行幾次?

如果你在迴圈一開始的地方加上 printf("count_emp=%d\n", count_emp); 的敘述, 會看到什麼數值印出來呢? 1, 2, 3, 4, ..., 7 or 0, 1, 2, ..., 6

請將上面迴圈改以 for 語法完成:

for (count_emp=0; count_emp<7; count_emp++)
{
    ...
}

2

特定結束資料控制的迴圈 (sentinel-controlled loop):

下面這一段程式由鍵盤讀入使用者輸入的多個成績資料, 使用者最後會輸入一個特定的結束資料 -99 (成績資料不會有這種數值) 來結束這個迴圈, 程式會計算出成績總和:
/* Compute the sum of a list of exam scores. */

#include <stdio.h>
#include <stdlib.h>

#define SENTINEL -99

int main(void)
{
    int sum = 0,   /* sum of scores input so far */
        score;     /* current input score	*/

    /* Accumulate sum of all scores.	*/
    printf("Enter first score (or %d to quit)> ", SENTINEL);
    scanf("%d", &score);       /* Get first score.	*/
    while (score != SENTINEL) 
    {
        sum += score;
        printf("Enter next score (%d to quit)> ", SENTINEL);
        scanf("%d", &score);   /* Get next score.	*/
    }
    printf("\nSum of exam scores is %d\n", sum);

    system("pause");
    return(0);
}

請拷貝上述程式至 dev C++ 編譯, 執行

請修改上面的程式, 將鍵盤輸入改為由 scores.dat 檔案輸入, 並且改為計算成績的平均值

  1. 改為由檔案輸入的話需要完成以下幾件事

    a. 下載 scores.dat 檔案
    b. FILE *inputfile; // 定義檔案指標變數
    c. inputfile = fopen("scores.dat", "r"); // 開啟檔案
    d. 檢查檔案是否開啟成功
    if (inputfile == 0)
    {
        printf("無法開啟檔案 scores.dat!!\n");
        exit(1);
    }
    e. fclose(inputfile); // 關閉檔案
    f. 由於由檔案輸入, 所以你不需要上面的 printf() 敘述來提示使用者輸入了
    g. 請將 scanf 敘述改為 fscanf(inputfile, "%d", &score);
  2. 要計算平均值, 你需要增加一個變數來記錄總共讀入幾筆資料, 檔案讀完以後將列印成績總和的敘述改為計算並且列印平均成績的敘述

3

到底什麼時候該用迴圈?

在課堂上解釋過 for 迴圈、while 迴圈、和 do-while 迴圈的基本語法以後, 知道每一個迴圈都會包括四個部份:

A. 初始,
B. 判斷是否繼續執行,
C. 迴圈主體, 以及
D. 迴圈變數增減,

所以看到一個程式裡使用迴圈的話, 我們可以瞭解到底程式在做什麼, 可是光光可以解釋程式的表現有點不夠, 重點在於到底什麼時候該用迴圈? 當我們需要程式有什麼表現的時候可以運用迴圈來完成? 並不是硬記下來說要做某個工作時該用兩層迴圈, 做另外一件事時用一層迴圈...

下面我們來討論幾個時機點

1. 當你需要重複做相同的事情很多次時

例如:
A. 需要重複輸入 5 個整數並列印它們的 3 次方,
B. 需要列印 5 次 "hello world\n",
C. 需要重複做 x = x + y; 5 次

也就是如果不用迴圈你需要寫下面的程式

    scanf("%d", &x);
    printf("%d\n", x*x*x);
    scanf("%d", &x);
    printf("%d\n", x*x*x);
    scanf("%d", &x);
    printf("%d\n", x*x*x);
    scanf("%d", &x);
    printf("%d\n", x*x*x);
    scanf("%d", &x);
    printf("%d\n", x*x*x);
	
    printf("hello world\n");
    printf("hello world\n");
    printf("hello world\n");
    printf("hello world\n");
    printf("hello world\n");
	
    x = x + y;
    x = x + y;
    x = x + y;
    x = x + y;
    x = x + y;

顯然你可以用迴圈來讓你的程式不會重複出現一樣的程式碼, 如下:

    for (i=0; i<5; i++)
    {
        scanf("%d", &x);
        printf("%d\n", x*x*x);
    }

    for (i=0; i<5; i++)
        printf("hello world\n");

    for (i=0; i<5; i++)
        x = x + y;

上面第三個例子比較沒有道理, 你會說為什麼不用 x = x + 5 * y; 就好了, 這是因為我們還沒有用陣列的原因, 讓 y 這個變數重複用了 5 次, 如果是 y[i] 的話就比較有道理了

重點是: 上面你看到一模一樣的程式碼重複出現多次, 所以使用迴圈, 有的時候甚至只出現兩次, 我們還是會用迴圈, 因為我們有一個基本原則: 程式裡一模一樣的敘述不會出現兩次, 否則

  1. 如果我們要修改 hello world 變成 Hello world, 你會發現要修改 5 次, 萬一漏掉修改一個, 程式的表現就不對了
  2. 用迴圈還有另外一個好處就是...既然重複出現了兩次, 也許有一天會需要改成 3 次, 那麼只需要改一個數字就好了
  3. 甚至可以讓使用者輸入一個數字, 讓使用者決定要重複幾次


不幸的是出現一模一樣程式碼的機會其實不太多.... 我們要稍微放寬一點

2. 當你需要重複做類似的事情很多次時


例如
A. 需要重複輸入多對整數 (a, b) 並且分別印出 a+b, a-b, a*b, a/b, a+b, a-b, a*b, a/b, ...
B. 需要列印出 "1\n", "3\n", "5\n", "7\n", ...
C. 需要把 26 個整數加總起來

也就是如果不用迴圈你需要寫下面的程式

    scanf("%d%d", &a, &b);
    printf("%d\n", a+b);
    scanf("%d%d", &a, &b);
    printf("%d\n", a-b);
    scanf("%d%d", &a, &b);
    printf("%d\n", a*b);
    scanf("%d%d", &a, &b);
    printf("%d\n", a/b);
... printf("1\n"); printf("3\n"); printf("5\n"); printf("7\n"); ... printf("99\n");
x = x + a; x = x + b; x = x + c; ... x = x + z;

這時候你也許覺得上面這樣子寫沒什麼不對的, 每一列都不太一樣啊! 迴圈主體裡面不是都是一樣的嗎??? 既然不一樣那不就不能用迴圈了?!

其實上面這些 概念上還是可以看成是重複的程式碼, 它們重複的部份是:

A. 輸入整數 (a, b), 印出 a 和 b 某種算術計算的結果
B. 列印某個整數資料
C. 把某個資料加到變數 x 裡

可是重複的部份裡面沒有完全一樣啊?? 雖然有重複的部份, 但是都有 "某種" 這樣子可以變化的東西, 其實這是一種 抽象化, 你搭乘公車的時候都要從某一站上車, 給某個金額的車資, 到某一站下車; 你買早餐的時候都要給付出某個金額買某一種餐點; 你要去上課都要去某一棟大樓的某一間教室...可是搭乘公車你會看成是同一件事, 買早餐會看成是同一件事, 上課會看成是同一件事, 都是每天會重複做的, 你心裡想的就是每天重複做一樣的事情吧, 不太會覺得每天都像做完全不同的事, 用完全不一樣的方法來處理

那麼不太一樣的地方該怎麼辦呢??? 如果是日常生活的話, 可能就有一些小規則, 比如說如果坐基隆市公車要付多少錢, 坐基隆客運由某站到某站要付多少錢, 坐台汽由某站到某站要付多少錢.... 那就是要判斷了.... if ..... 就出現了, 如果比較規律化的話, 可以運用變數、陣列、迴圈、或是函式的語法再進一步簡化

    for (i=0; i<20; i++)
    {
        scanf("%d%d", &a, &b);
        if (i%4==0)
printf("%d\n", a+b); else if (i%4==1)
printf("%d\n", a-b); else if (i%4==2)
printf("%d\n", a*b); else if (i%4==3)
printf("%d\n", a/b); } for (i=0; i<50; i++) printf("%d\n", 2*i+1); for (i=0; i<26; i++) switch (i) { case 0: x = x + a; break; case 1: x = x + b; break; ... case 25: x = x + z; }

上面第二個寫法是很好的, 不同的地方完全用變數的運算來取代, 很簡潔!!

第三個寫法一定是不好的, 用了 26 個變數, 還用很大的一個 switch 敘述, 通常這時就是會使用陣列變數的時候了, 如果資料不是放在 a, b, c, ..., z 的變數理, 而是放在 data[0], data[1], ..., data[25] 這 26 個變數裡面, 程式就可以簡化成

    for (i=0; i<26; i++)
        x = x + data[i];

這時候所有不同的地方完全用變數 i 以及陣列變數 data[i] 的存取來取代, 很簡潔!!

第一個的寫法勉強可以, 要進一步簡化的話可以用 函式 或是 函式指標的陣列來完成, 類似下面的作法

    void printArithmetic(int type, int a, int b);

    for (i=0; i<20; i++)
    {
        scanf("%d%d", &a, &b);
        printArithmetic(i%4, a, b);
    }

    void printArithmetic(int type, int a, int b)
    {
        if (type==0)
printf("%d\n", a+b); else if (type==1)
printf("%d\n", a-b); else if (type==2)
printf("%d\n", a*b); else if (type==3)
printf("%d\n", a/b); }

現在是迴圈內的東西變成完全一樣了, 在概念上簡化了, 但是程式碼其實幾乎是一樣的, 另外一種方式是

    int add(int a, int b) { return a+b; }
    int sub(int a, int b) { return a-b; }
    int mul(int a, int b) { return a*b; }
    int div(int a, int b) { return a/b; }

    typedef int (*FP)(int, int);
    FP op[] = {add, sub, mul, div};

    for (i=0; i<20; i++)
    {
        scanf("%d%d", &a, &b);
        printf("%d\n",(*op[i%4])(a,b));
    }

這個例子因為用到函式指標以及陣列, 比較難懂一些, 可以以後再回來仔細看, 目前你就只要注意到: 迴圈內的東西變成完全一樣了, 不一樣的地方用 op 陣列和 變數 i 來處理

接下來再看一個例子, 上星期的實習需要一個程式印出

On the first day of Christmas
my true love sent to me:
A Partridge in a Pear Tree

On the second day of Christmas
my true love sent to me:
2 Turtle Doves
and a Partridge in a Pear Tree

On the third day of Christmas
my true love sent to me:
3 French Hens
2 Turtle Doves
and a Partridge in a Pear Tree

這樣也有看到重複性的東西 @@

有啊! 每一段都是 On the .... 是有重複, 但是每一段裡面好像變化還蠻大的

人很厲害 (至少受過訓練的工程師是這樣), 人都很會抽象化, 也都會故意把不太一樣的東西看成是一樣的, 你看下面的東西會覺得重複性相當大了吧!

On the xxx day of Christmas
my true love sent to me:
.

On the xxx day of Christmas
my true love sent to me:
..

On the xxx day of Christmas
my true love sent to me:
...

有一種我很會的感覺吧!! 這就是我都會忘記每一個人名字的原因啊, 都是同學嘛

    const char *numbers[] = {"first", "second", "third"};
    for (i=0; i<3; i++)
        printf("On the %s day of Christmas\nmy true love sent to me:\n", numbers[i]);

那個 ... 就是我故意假裝沒看到的東西, 我還用 . 代表一列, .. 代表兩列, ... 代表三列呢, ... 個數不太一樣, 看下面的程式怎樣印這些 ...

    const char *numbers[] = {"first", "second", "third"};
    for (i=0; i<3; i++)
    {
        printf("On the %s day of Christmas\nmy true love sent to me:\n", numbers[i]);
        for (j=0; j<i+1; j++) printf(".");
        printf("\n");
    }

好, 玩夠了, 接下來要把它們變成不一樣的東西

On the first day of Christmas
my true love sent to me:
1

On the second day of Christmas
my true love sent to me:
21

On the third day of Christmas
my true love sent to me:
321

這也容易

    const char *numbers[] = {"first", "second", "third"};
    for (i=0; i<3; i++)
    {
        printf("On the %s day of Christmas\nmy true love sent to me:\n", numbers[i]);
        for (j=0; j<i+1; j++) printf("%d", i+1-j);
        printf("\n");
    }

接下來換成字串

On the first day of Christmas
my true love sent to me:
A Partridge

On the second day of Christmas
my true love sent to me:
2 Turtle
A Partridge

On the third day of Christmas
my true love sent to me:
3 French
2 Turtle
A Partridge

稍微簡化了一點

    const char *numbers[] = {"first", "second", "third"};
    const char *lines[] = {"A Partridge", "2 Turtle", "3 French"};
    for (i=0; i<3; i++)
    {
        printf("On the %s day of Christmas\nmy true love sent to me:\n", numbers[i]);
        for (j=0; j<i+1; j++) printf("%s\n", lines[i-j]);
        printf("\n");
    }

用陣列 lines 和 變數 i, j 來印出不同的字串, 剩下的就是處理 i 為 0 時其實要印 "A Partridge", i 大於 0 時要印 "and a Partridge", 你知道不同時候要有不同的表現就是用 if 來判斷, if (i==0) ...

還有最後的換列 printf("\n"); 也需要一點處理, 因為並不是在每一段最後都要換列, 又是有一點點不同的地方.... 用 if 來判斷吧, if (i<2) ...

4

迴圈應用練習:

請撰寫 for 迴圈, 如果使用者輸入 7, 在螢幕上顯示下面七列

***
******
*********
************
*********
******
***

如果使用者輸入 3, 在螢幕上顯示下面三列

***
******
***

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int nlines;
    int i, j;

    do 
    {
        printf("Please input an odd line number: ");
        scanf("%d", &nlines);
    }
    while (nlines%2==0);

    for (i=0; i<nlines/2+1; i++)
    {
        for (j=0; j<i+1; j++)
            printf("***");
        printf("\n");
    }

    for (i=nlines/2+1; i<nlines; i++)
    {
        for (j=0; j<nlines-i; j++)
            printf("***");
        printf("\n");
    }

	system("pause");
	return 0;
}
請注意: 在迴圈內容不太多的情況下, 迴圈控制變數 i, j, k, ... 是在程式裡唯一不要求你仔細命名的變數, 常常在程式裡也會重複使用, 但是第二次使用時會避免使用先前殘留下來的數值, i, j, k, 這種變數沒有適當命名的變數是避免用來在兩段程式之間傳遞資料的

程式設計課程 首頁

製作日期: 98/09/13 by 丁培毅 (Pei-yih Ting)
E-mail: pyting@mail.ntou.edu.tw TEL: 02 24622192x6615
海洋大學 電機資訊學院 資訊工程學系 Lagoon