4.2 while 迴圈
while 迴圈最直觀——「只要條件成立,就一直做某件事」。它特別適合那些「不知道要重複幾次」的場景,比如「讀取輸入直到遇到 0」。
while 迴圈的結構:
while (條件式) {
程式碼區塊
}
執行流程:
- 檢查
條件式是否為 true - 如果是,執行
程式碼區塊,然後回到步驟 1 - 如果否,跳過迴圈,繼續執行後面的程式
¶把問題翻成 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
這種「跑固定圈數」其實用後面的 for(4.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 = 1、1 \div 2 = 0,這正是它終究會歸零、迴圈保證停下來的原因。
範例 3:計算位數總和
再看一個形狀幾乎一樣、只是每圈多做一點事的例子:
題目:給一個正整數 n(可以很大,但仍在
long long範圍內),求它每一位數字加起來的總和。例如 n = 2024,答案是 2 + 0 + 2 + 4 = 8。
如果 n 只有兩三位,你可以用 n % 10、n / 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、換不動了
因為 n、x 可以到 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 的骨架都一樣,只是每圈要維護的狀態愈來愈多——先把三要素想清楚,再多的狀態也只是「每圈照著更新」而已。
¶動手試試看
先在紙上把每題的三個要素(重複做的事/停止條件/每圈的變化)寫出來,再動手寫程式:
- (暖身) 讀入一個正整數 n,用
while印出 n、n-1、…、1(倒數)。 - (累加) 讀入一個正整數 n,算出 1 + 2 + \dots + n 的總和並輸出。(提示:多開一個變數當「目前累加到的總和」,宣告在迴圈外。)
- (情境) 存錢筒現在有 M 元,每天固定存 x 元。讀入 M、x 和目標金額 G,問幾天後存款會首次達到 G 元以上?