National Chengchi University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try a Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

1

Local AI

--

Uedu Code

--

政治大學 AQI 58 25°C PM2.5 12
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 05

迴圈

for、while、do-while、for-each、break 與 continue
Interaction實作 20 minute(s)

Learning Objectives

  • 說明 for 迴圈三個部分(初始化、條件、更新)的執行順序,推算迴圈會跑幾圈
  • 分辨 while 與 do-while:while 可能一次都不執行,do-while 至少執行一次
  • 用 for-each 依序取出陣列的每個元素,用巢狀迴圈處理「列 × 行」這類問題
  • 用 break、continue 與標籤 break 控制迴圈提早結束或跳過某一圈
  • 避開常見錯誤:差一錯誤、無窮迴圈、迴圈變數的有效範圍與同名變數

為什麼需要迴圈

要印出 1 到 100、把 50 位學生的分數加總、反覆詢問直到使用者輸入正確為止,這些工作都是「同樣的步驟做很多次」。 把同一行程式複製 100 次既費力又容易出錯,迴圈(loop)讓你只寫一次,交給電腦重複執行。

Java 有四種迴圈:

迴圈 適合的情況
for 事先知道要跑幾圈,或要用一個計數器
while 不知道要跑幾圈,只知道「條件成立就繼續」
do-while 和 while 一樣,但本體至少要執行一次
for-each 依序取出陣列或集合裡的每個元素,不需要索引

學過 Python 的話:Java 的 for (int i = 0; i < 5; i++) 相當於 Python 的 for i in range(5); Java 的 for-each for (int s : scores) 相當於 Python 的 for s in scores。 不同的是 Java 用大括號 { } 圍出迴圈本體,不靠縮排;條件一定要放在小括號裡,而且結果必須是 boolean。

for 迴圈的三個部分

for (int i = 1; i <= 4; i++) {
    System.out.println(i);
}

小括號裡用兩個分號隔成三個部分,執行順序是:

  1. 初始化 int i = 1:只在最開始執行一次
  2. 條件 i <= 4:每一圈開始前檢查;true 就執行本體,false 就離開迴圈
  3. 執行本體(大括號裡的程式)
  4. 更新 i++:本體執行完才做,做完回到第 2 步

所以順序是「初始化 → 條件 → 本體 → 更新 → 條件 → 本體 → 更新 → …」,直到條件為 false。 最後一次檢查時 i 已經是 5,條件不成立,本體不會再執行。

更新不一定是 i++:i += 2 每次加 2、k-- 倒數都可以。在動畫裡,本體跑完之後執行箭頭會回到 for 那一行,那一步就是「更新、再檢查條件」。

for 迴圈逐步執行

盯著左邊的 i 和 sum。箭頭每回到第 5 行一次,就是做完 i++ 再檢查條件;i 變成 5 時條件不成立,跳到第 9 行,而且 i 從變數區消失了:在 for 小括號裡宣告的變數,只在迴圈裡有效。

while 與 do-while

while 只有條件,沒有初始化和更新的位置:

int n = 20;
while (n > 1) {      // 條件成立才執行本體
    n = n / 2;
}

不知道要跑幾圈、只知道「什麼時候停」的時候用 while 比較自然,例如「一直除以 2,直到剩下 1」「一直讀取輸入,直到使用者輸入 0」。 條件一開始就不成立的話,本體一次都不會執行。

do-while 把條件放在最後面,所以本體至少執行一次,然後才檢查條件決定要不要再來一次:

do {
    System.out.print("請輸入 1~5:");
    choice = sc.nextInt();
} while (choice < 1 || choice > 5);   // 注意結尾的分號

「先做一次再說」的情況(例如顯示選單、要求輸入,輸入不合法就再問一次)很適合 do-while。 while (…) 後面那個分號是 do-while 語法的一部分,忘了寫會編譯錯誤。

while 可能一次都不跑,do-while 至少跑一次

前半段看 n 怎麼從 20 一路除到 1,count 記錄除了幾次。後半段的 x 是 100:第 13 行的 while (x < 10) 一開始就不成立,箭頭直接跳過本體;第 18 行的 do-while 卻會先執行一次本體,到第 21 行才檢查條件。

想一想:迴圈會跑幾圈

1.

下面的程式會印出什麼?

int n = 0;
do {
    n += 3;
} while (n < 10);
System.out.println(n);
2.

下面的程式會印出什麼?

int c = 0;
for (int i = 0; i < 5; i++) {
    for (int j = i; j < 5; j++) {
        c++;
    }
}
System.out.println(c);
3.

下面的程式會印出什麼?

for (int i = 0; i < 3; i++) {
    // 什麼都不做
}
System.out.println(i);
第 1 次作答

for-each 與巢狀迴圈

for-each(也叫加強型 for)依序取出陣列或集合裡的每個元素,不需要自己管索引:

int[] scores = {72, 85, 90};
for (int s : scores) {      // 讀作「對 scores 裡的每一個 s」
    System.out.println(s);
}

冒號左邊是變數(型別要和元素一樣),右邊是要走訪的陣列。陣列的細節(怎麼建立、索引、長度)在第 06 課介紹。 for-each 只能從頭到尾依序讀取;需要知道「現在是第幾個」、要倒著走,或要修改陣列裡的元素時,還是要用一般的 for 搭配索引。

巢狀迴圈是在迴圈本體裡再放一個迴圈。外層每跑一圈,內層就從頭到尾完整跑一輪, 適合處理「列 × 行」的資料,例如乘法表、表格、二維陣列。外層跑 3 圈、內層每次跑 3 圈,內層本體一共執行 3 × 3 = 9 次。 內外層的計數器要用不同名稱(習慣用 i、j),否則會編譯錯誤。

for-each 加總與小型乘法表

前半段看 s:for-each 每一圈把陣列的下一個元素放進 s,你不會看到索引。後半段是巢狀迴圈:注意 j 每次都從 1 重新開始,跑完 3 圈後 j 消失,i 才加 1。

break、continue 與標籤

  • break:立刻離開整個迴圈,跳到迴圈後面的第一行
  • continue:跳過這一圈剩下的程式,直接進入下一圈(for 迴圈會先執行更新,再檢查條件)

break 與 continue 只影響最內層的那個迴圈。在巢狀迴圈裡想一次離開外層,可以替外層迴圈加上標籤(label):

outer:                               // 標籤:名稱加冒號,放在迴圈前面
for (int r = 1; r <= 3; r++) {
    for (int c = 1; c <= 3; c++) {
        if (r * c == 4) {
            break outer;             // 直接離開外層迴圈
        }
    }
}

continue outer; 也可以用:結束內層,直接進入外層的下一圈。標籤只用在真的需要一次跳出多層的時候; 迴圈太複雜時,把它拆成一個方法、找到答案就 return(第 07 課),通常更好讀。

無窮迴圈:條件永遠成立的迴圈,例如 while (true) { … } 或 for (;;) { … }。刻意寫成無窮迴圈時,本體裡一定要有 break(或 return)作為出口; 不小心寫出來的無窮迴圈,最常見的原因是忘了更新計數器,例如 while (i < 10) 的本體裡沒有 i++。

continue 跳過、break 離開、標籤 break 跳出兩層

第一個迴圈:i 是偶數時第 6 行 continue,箭頭直接回到第 4 行,不會印出;i 是 5 時第 9 行 break,6 根本不會被檢查。第二個迴圈:r * c == 4 時 break outer,內外兩層一起結束,直接跳到第 23 行。

常見錯誤

差一錯誤(off-by-one error):迴圈多跑或少跑一圈,是寫迴圈最常見的錯誤。

int[] a = {10, 20, 30};                 // 索引是 0、1、2,a.length 是 3
for (int i = 0; i <= a.length; i++) {   // 錯:i 會跑到 3
    System.out.println(a[i]);           // i 是 3 時拋出 ArrayIndexOutOfBoundsException
}

從 0 開始數、要跑 n 次時,條件寫 i < n;從 1 開始數到 n,才寫 i <= n。不確定時,代入最小的情況(n = 1)想一遍迴圈跑幾次。

變數的有效範圍(scope):

  • 在 for 小括號裡宣告的變數,只在迴圈裡有效;迴圈結束後再用會編譯錯誤 cannot find symbol
  • 在迴圈本體大括號裡宣告的變數,每一圈都是新的,離開本體就不存在;要在迴圈外累加的變數(例如 sum),一定要宣告在迴圈前面
  • 迴圈裡不能宣告和外面同名的區域變數:
int i = 0;
for (int i = 0; i < 3; i++) { }   // 編譯錯誤:variable i is already defined in method main(String[])

Python 允許這樣寫(迴圈會直接改掉外面的 i),Java 不允許,可以防止你不小心蓋掉外面的變數。 兩個前後並列的迴圈各自宣告 int i 則沒有問題,因為第一個 i 在第二個迴圈開始前就已經消失了。

分號放錯位置:for (int i = 0; i < 3; i++); 小括號後面多一個分號,迴圈本體變成空的,下面的大括號只會執行一次。while (…); 更糟,可能變成無窮迴圈。

想一想:break、continue 與常見錯誤

1.

下面的程式會印出什麼?

outer:
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        if (j == 1) continue outer;
        System.out.print(i + "" + j + " ");
    }
}
System.out.println();
2.

下面的程式執行到最後會拋出什麼例外?

int[] a = {1, 2, 3};
for (int i = 0; i <= a.length; i++) {
    System.out.println(a[i]);
}
3.

下面的程式會印出什麼?

int i = 0;
for (int i = 0; i < 3; i++) {
    System.out.print(i);
}
第 1 次作答

動手寫:星號三角形

用巢狀迴圈印出 4 列星號:第 1 列 1 顆、第 2 列 2 顆……第 4 列 4 顆,最後印出一共用了幾顆。外層迴圈已經寫好,請完成內層迴圈與計數。

你的輸出
預期輸出

重點回顧

  • for (初始化; 條件; 更新):初始化只做一次,之後每圈「條件 → 本體 → 更新」,條件為 false 時離開
  • while 先檢查條件,可能一次都不執行;do { … } while (條件); 先執行再檢查,至少執行一次,結尾要有分號
  • for-each for (int s : scores) 依序取出每個元素,不需要索引;要索引或要修改元素時用一般 for
  • 巢狀迴圈:外層每跑一圈,內層完整跑一輪
  • break 離開迴圈、continue 跳過這一圈,都只影響最內層;加上標籤(break outer;)可以一次跳出外層
  • 常見錯誤:差一錯誤(< 和 <=)、忘了更新計數器造成無窮迴圈、迴圈結束後使用迴圈內宣告的變數、在迴圈裡宣告和外面同名的變數
這一課完成了!
Next lesson:陣列
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。