4.2 while 迴圈

while 迴圈最直觀——「只要條件成立,就一直做某件事」。它特別適合那些「不知道要重複幾次」的場景,比如「讀取輸入直到遇到 0」。

while 迴圈的結構:

while (條件式) {
    程式碼區塊
}

執行流程:

  1. 檢查 條件式 是否為 true
  2. 如果是,執行 程式碼區塊,然後回到步驟 1
  3. 如果否,跳過迴圈,繼續執行後面的程式
圖 4-1:while 迴圈執行流程

把問題翻成 while:三個要素

承接 4.1 的三個要素,動筆寫 while 之前先把它們想清楚,各自對應到程式的一個位置:

  • 重複做的事 → 放進 { } 裡。
  • 停止條件 → 寫在 while ( ) 的括號裡。注意括號裡寫的是「還要繼續的條件」:成立才做下一圈,一變成 false 就停。
  • 每圈的變化 → 要在 { }親手更新while 不會自動幫你加。

套成一個模板:

初始化計數器 / 狀態;
while (還要繼續的條件) {
    做這一圈的事;
    更新狀態;        // ← 最容易忘的一行,忘了就變無窮迴圈
}

之後每寫一個 while,就回頭對照這三格有沒有到齊。

範例程式碼

先用一個最簡單的形狀熟悉 while 的三要素怎麼落地,再看它真正的拿手好戲。

範例 1:印出 1 到 10(先熟悉形狀)

#include<iostream>
using namespace std;
int main() {
    int i = 1;              // 初始化計數器

    while (i <= 10) {        // 條件:i 不超過 10
        cout << i << "\n";  // 輸出 i
        i += 1;             // i 加 1
    }

    return 0;
}

執行結果:

1
2
3
4
5
6
7
8
9
10

這種「跑固定圈數」其實用後面的 for4.3)寫起來更順手;這裡只是先借最單純的例子,把三要素、尤其是「更新那一行」看清楚。動手試試看:把迴圈條件 i <= 10 改成 i < 10,看看輸出有什麼變化,為什麼會差 1?

範例 2:折半歸零要幾次

題目:給一個正整數 n,每次把它整數除以 2(小數捨去),問幾次會變成 0?(OJ 練習:折半歸零(halfzero)

這題才是 while 真正的拿手好戲——次數事先算不出來,得一路除下去才知道。照三個要素想:重複做的事是「除以 2」,停止條件是「變成 0 就停」(也就是還大於 0 才繼續),每圈的變化是 n 自己被除小。

#include<iostream>
using namespace std;
int main() {
    int n;
    cin >> n;

    int count = 0;          // 計數器:記錄除了幾次
    while (n > 0) {          // 只要還沒歸零就繼續
        n /= 2;             // 整數除法,小數直接捨去
        count += 1;         // 除了一次
    }

    cout << count << "\n";
    return 0;
}

逐圈追蹤(假設輸入為 13):

圈數 進迴圈前 n n > 0 n /= 2 count
1 13 成立 → 做 6 1
2 6 成立 → 做 3 2
3 3 成立 → 做 1 3
4 1 成立 → 做 0 4
0 不成立 → 停 4

所以輸出 4。注意整數除法是無條件捨去:3 \div 2 = 11 \div 2 = 0,這正是它終究會歸零、迴圈保證停下來的原因。

範例 3:計算位數總和

再看一個形狀幾乎一樣、只是每圈多做一點事的例子:

題目:給一個正整數 n(可以很大,但仍在 long long 範圍內),求它每一位數字加起來的總和。例如 n = 2024,答案是 2 + 0 + 2 + 4 = 8

如果 n 只有兩三位,你可以用 n % 10n / 10 % 10 … 一位一位手算。但位數不固定時就得靠迴圈:每圈撈出最後一位加進總和,再把最後一位切掉,切到 n 變成 0 為止。這和折半歸零根本是同一個骨架,只是把「除以 2、數次數」換成「除以 10、把餘數加起來」:

  • 撈最後一位:n % 10(除以 10 的餘數就是個位)
  • 切掉最後一位:n /= 10
  • 停止條件:n 變成 0(還大於 0 才繼續)
#include<iostream>
using namespace std;
int main() {
    long long n;
    cin >> n;

    long long sum = 0;       // 累加器:目前的數字總和
    while (n > 0) {           // 只要還沒切光就繼續
        sum += n % 10;       // 撈出最後一位,加進總和
        n /= 10;             // 把最後一位切掉
    }

    cout << sum << "\n";
    return 0;
}

逐圈追蹤(假設輸入為 2024):

圈數 進迴圈前 n n % 10(撈出的位數) sum 累加後 n /= 10
1 2024 4 4 202
2 202 2 6 20
3 20 0 6 2
4 2 2 8 0
0 8

跟折半歸零對照著看:兩題都是「一直把 n 變小到 0」,差別只在每圈做什麼——一個數次數、一個把撈出的位數累加。看懂一個,另一個就通了。

範例 4:空瓶兌換(多個狀態一起變)

前面幾題每圈只動一兩個變數。最後看一個好幾個狀態同時變化的例子,while 一樣罩得住。

題目:一瓶可樂喝完會剩一個空瓶,x 個空瓶可以換 1 瓶新的可樂(保證 x \ge 2,不然永遠換不完)。你買了 n 瓶,問最後總共能喝到幾瓶、以及最後剩幾個空瓶?(OJ 練習:空瓶兌換(bottlexchg)

換瓶的次數事先不知道,正好用 while。這裡要同時追蹤兩個狀態:目前手上的空瓶數、以及至今喝到的總瓶數。每圈的邏輯是「只要空瓶夠換,就換一批」:

  • 這一輪能換到幾瓶:empty / x
  • 喝到的總數增加:drunk += 換到的
  • 空瓶更新:換來的可樂喝完也變空瓶,所以 empty = empty % x + 換到的
  • 停止條件:空瓶不足 x、換不動了

因為 nx 可以到 10^{18},變數要用 long long

#include<iostream>
using namespace std;
int main() {
    long long n, x;
    cin >> n >> x;

    long long drunk = n;     // 喝到的總瓶數:一開始把買的都喝掉
    long long empty = n;     // 手上的空瓶數
    while (empty >= x) {      // 只要空瓶夠換一瓶就繼續
        long long got = empty / x;   // 這一輪換到幾瓶
        drunk += got;                // 換到的也喝掉
        empty = empty % x + got;     // 沒換掉的空瓶 + 新產生的空瓶
    }

    cout << drunk << " " << empty << "\n";
    return 0;
}

逐圈追蹤(假設輸入為 4 2,即買 4 瓶、2 個空瓶換 1 瓶):

圈數 進迴圈前 empty empty >= 2 換到 got drunk 更新後 empty
1 4 成立 → 換 2 6 2
2 2 成立 → 換 1 7 1
1 不成立 → 停 7 1

所以輸出 7 1:總共喝到 7 瓶、最後剩 1 個空瓶。三個例子一路看下來,while 的骨架都一樣,只是每圈要維護的狀態愈來愈多——先把三要素想清楚,再多的狀態也只是「每圈照著更新」而已。

動手試試看

先在紙上把每題的三個要素(重複做的事/停止條件/每圈的變化)寫出來,再動手寫程式:

  1. (暖身) 讀入一個正整數 n,用 while 印出 nn-1、…、1(倒數)。
  2. (累加) 讀入一個正整數 n,算出 1 + 2 + \dots + n 的總和並輸出。(提示:多開一個變數當「目前累加到的總和」,宣告在迴圈。)
  3. (情境) 存錢筒現在有 M 元,每天固定存 x 元。讀入 Mx 和目標金額 G,問幾天後存款會首次達到 G 元以上?