語法書 / AA 競程語法書 下冊 / 第十二單元 / 以範圍為基礎的 for 迴圈

12.4 以範圍為基礎的 for 迴圈

「把容器裡的每個元素看一遍」大概是你寫過最多次的迴圈:

✘ 囉唆寫法:

for (int i = 0; i < (int)v.size(); i++) {
    cout << v[i] << ' ';
}

C++11 起有更直白的寫法——以範圍為基礎的 for 迴圈(range-based for loop,多數人直接叫它英文名):

✔︎ 省力寫法:

for (int x : v) {
    cout << x << ' ';
}

意思是:v 裡的每個元素 x,執行一次迴圈本體。不用索引、不用 size()、不用煩惱 10.4 的無號陷阱——範圍的頭尾編譯器自己管。陣列、vector、string 通通適用:

#include <bits/stdc++.h>
using namespace std;

int main() {
    int a[3] = {2, 5, 9};
    for (int x : a) cout << x << ' ';        // 陣列
    cout << '\n';

    vector<int> v = {5, 3, 2, 7};
    for (int x : v) cout << x << ' ';        // vector
    cout << '\n';

    string s = "good";
    for (char c : s) cout << c << ' ';       // string:一次一個字元
    cout << '\n';
    return 0;
}

執行結果:

2 5 9 
5 3 2 7 
g o o d

它不是魔法——對 vector 來說,編譯器把它展開成 10.5 學過的 iterator 迴圈:

for (auto it = v.begin(); it != v.end(); it++) {
    int x = *it;
    // 迴圈本體
}

要修改元素:加 &

看清楚展開式的 int x = *it;——每一圈的 x 是元素的複本(上冊 7.5 傳值的既視感)。改 x 改的是複本,容器毫髮無傷。想真的動到元素,把 x 宣告成上一節的參考

#include <bits/stdc++.h>
using namespace std;

int main() {
    int n;
    cin >> n;
    vector<int> v(n);
    for (int &x : v) cin >> x;      // 加 &:x 是元素的別名,讀入直接存進 v
    for (int &x : v) x *= 2;        // 全部乘 2
    for (int x : v) cout << x << ' ';   // 只讀不改:不加 & 也行
    cout << '\n';
    return 0;
}

執行結果(輸入 34 7 1):

8 14 2

搭配 12.2 就是高手程式碼裡的 for (auto &x : v)——元素型態長的時候讓編譯器推,& 照加。

range-based for 的極限

省力是有代價的,三件事它做不到:

  1. 拿不到索引值——迴圈裡只有元素,沒有「這是第幾格」。要印編號、要同時碰 v[i]v[i + 1],回頭用傳統 for。
  2. 只能從頭走到尾——不能倒著走、不能跳著走、不能中途從第 k 格開始。
  3. 不能一邊走一邊改容器長度——邊掃邊 push_back 會出事:容器變長時元素可能整批搬家(10.2 的抽象理解提過),迴圈手上記的位置會變成空號(UB)。

它是「全部看一遍」的專用工具;範圍一旦不是「全部」,就不是它的戲了。

動手試試看:把你解好多數列(簡單版)相鄰城市列表的舊程式碼翻出來,用 range-based for 改寫所有「掃過整個 vector」的迴圈(讀入的迴圈記得加 &),重新提交確認照樣 AC。改完數數看少打了幾個字。