第 18.5 節

範圍 for 循環

0瀏覽次數0訪問次數--跳出率--平均停留

本節解決什麼問題

傳統的 for 循環遍歷容器需要寫三要素(初始化、條件、遞增),容易出錯:下標越界、用錯邊界條件、類型不匹配等等。

範圍 for 循環讓你寫"對容器中每個元素做..."的代碼時,不再需要關心下標和迭代器細節,代碼更簡潔安全。

這個特性是什麼

範圍 for 循環(Range-based for loop)是 C++11 引入的語法糖,讓你直接遍歷容器(或任何有 begin() / end() 的類型)中的每個元素,編譯器自動生成對應的迭代器代碼。

C++ 標準版本

C++11(基礎),C++20 增加了初始化語句支持。

需要的頭文件

不需要額外頭文件。範圍 for 是語言特性。但需要遍歷的容器需要包含相應的頭文件(如 <vector>)。

基本語法

for (元素类型 变量名 : 容器)
{
    // 使用变量名
}

// 推荐:不修改元素用 const auto&
for (const auto& 变量名 : 容器)
{
    // 只读访问
}

// 要修改元素用 auto&
for (auto& 变量名 : 容器)
{
    // 可以修改元素
}

常用用法

用法說明何時使用
for (auto x : c)值拷貝遍歷元素類型小(int/double 等)
for (const auto& x : c)只讀引用遍歷最常用,不想拷貝大對象
for (auto& x : c)可修改引用遍歷需要修改容器中元素
for (int x : {1, 2, 3})遍歷初始值列表快速遍歷已知小集合

什麼時候不用範圍 for

範圍 for 適合"逐個訪問元素"。如果你的邏輯依賴下標、相鄰元素、反向遍歷、跳着遍歷,或者需要在遍歷過程中增刪容器元素,傳統 for 或迭代器循環反而更清楚。

需求推薦寫法原因
只讀訪問每個元素範圍 for最簡潔
修改每個元素的值for (auto& x : c)直接改原元素
需要下標傳統 for (size_t i = 0; ...)下標是邏輯的一部分
比較相鄰元素傳統 for要訪問 ii - 1
遍歷時刪除元素迭代器循環 / 算法範圍 for 容易觸發迭代器失效

示例代碼

示例 1:用範圍 for 遍歷 vector

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> v = {10, 20, 30, 40, 50};

    // 范围 for 遍历
    std::cout << "elements: ";
    for (int n : v)
    {
        std::cout << n << " ";
    }
    std::cout << "\n";

    return 0;
}

運行結果

elements: 10 20 30 40 50 

示例 2:在示例 1 基礎上,const auto& 和 auto& 的區別

#include <iostream>
#include <vector>
#include <string>

int main()
{
    std::vector<std::string> names = {"Alice", "Bob", "Charlie"};

    // const auto&:只读遍历,不拷贝(推荐)
    std::cout << "const auto&: ";
    for (const auto& s : names)
    {
        std::cout << s << " ";
        // s = "X";  // ❌ 编译错误!const 引用不能修改
    }
    std::cout << "\n";

    // auto&:可修改遍历
    for (auto& s : names)
    {
        s = s + "!";  // 修改原容器中的元素
    }
    std::cout << "after modify: ";
    for (const auto& s : names)
    {
        std::cout << s << " ";
    }
    std::cout << "\n";

    return 0;
}

運行結果

const auto&: Alice Bob Charlie 
after modify: Alice! Bob! Charlie! 

示例 3:在示例 2 基礎上,遍歷初始值列表和 map

#include <iostream>
#include <map>
#include <string>

int main()
{
    // 直接遍历初始值列表
    std::cout << "init list: ";
    for (int n : {3, 1, 4, 1, 5, 9})
    {
        std::cout << n << " ";
    }
    std::cout << "\n";

    // 遍历 map
    std::map<std::string, int> scores = {
        {"Alice", 85},
        {"Bob", 92},
        {"Charlie", 78}
    };

    std::cout << "scores:\n";
    for (const auto& pair : scores)
    {
        std::cout << "  " << pair.first << ": " << pair.second << "\n";
    }

    return 0;
}

運行結果

init list: 3 1 4 1 5 9 
scores:
  Alice: 85
  Bob: 92
  Charlie: 78

示例 4:在示例 3 基礎上,C++17 結構化綁定遍歷 map

#include <iostream>
#include <map>
#include <string>

int main()
{
    std::map<std::string, int> scores = {
        {"Alice", 85},
        {"Bob", 92},
        {"Charlie", 78}
    };

    // C++17 结构化绑定:直接解包 key 和 value
    std::cout << "scores (structured binding):\n";
    for (const auto& [name, score] : scores)
    {
        std::cout << "  " << name << ": " << score << "\n";
    }

    return 0;
}

運行結果

scores (structured binding):
  Alice: 85
  Bob: 92
  Charlie: 78

示例 5:在示例 4 基礎上,需要下標時不要硬用範圍 for

#include <cstddef>
#include <iostream>
#include <string>
#include <vector>

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    // vector 是动态数组,元素数量可以在运行时变化。
    std::vector<int> temperatures = {22, 23, 21, 25, 24};

    std::cout << "all temperatures: ";
    for (int t : temperatures)
    {
        std::cout << t << " ";
    }
    std::cout << "\n";

    std::cout << "changes between adjacent days:\n";
    for (std::size_t i = 1; i < temperatures.size(); ++i)
    {
        int change = temperatures[i] - temperatures[i - 1];
        std::cout << "  day " << i << " -> day " << (i + 1)
                  << ": " << change << "\n";
    }

    return 0;
}

運行結果

all temperatures: 22 23 21 25 24
changes between adjacent days:
  day 1 -> day 2: 1
  day 2 -> day 3: -2
  day 3 -> day 4: 4
  day 4 -> day 5: -1

運行結果

見上方每個示例的"運行結果"。

示例中的關鍵語法解釋

示例講了什麼新出現的語法爲什麼這樣寫注意事項
示例 1最基礎的範圍 forfor (int n : v)直接讀懂意圖:"對 v 中每個元素 n"這種寫法會拷貝每個 int,但 int 很小無所謂
示例 2const auto& vs auto&const auto&auto&const auto& 避免拷貝;auto& 可修改元素遍歷 string 等大對象時一定要用引用
示例 3遍歷初始值列表和 map遍歷 {...} 和 map初始值列表可以直接放在 for 裏map 遍歷出來的是 std::pair
示例 4結構化綁定解包 mapfor (const auto& [k, v] : m)C++17 語法,key/value 分別起名更清晰如果不用 C++17,可以用示例 3 的方式
示例 5需要下標時的寫法std::size_t i、傳統 for相鄰元素比較離不開下標不要爲了使用新語法犧牲清晰度

常見錯誤

錯誤 1:在遍歷過程中增刪元素

for (auto x : v)
{
    v.push_back(x * 2);  // ❌ 可能导致迭代器失效、无限循环!
}

正確做法:先收集到新容器,循環結束後統一操作,或者用傳統迭代器循環。

錯誤 2:auto 不加引用,修改無效

for (auto s : names)
{
    s = "X";  // 修改的是拷贝!
}
// names 没有变化!

正確做法:要修改容器元素,用 for (auto& s : names)

錯誤 3:遍歷時用下標訪問另一個容器

for (int n : v1)
{
    v2[i++] = n;  // i 从哪来?
}

正確做法:需要下標時,傳統 for 循環可能更合適,或者用枚舉技巧。

使用建議

  1. 不修改元素:用 const auto& — 這是默認選擇。
  2. 修改元素:用 auto& — 明確意圖是修改。
  3. 基本類型(int/double)且不修改:可以用 auto — 拷貝開銷可忽略。
  4. 需要下標時老老實實用傳統 for — 範圍 for 不適合所有場景。
  5. 遍歷 map 優先用 [key, value] 結構化綁定(C++17 起)。

小結

  • 範圍 for 遍歷容器的語法是 for (const auto& x : container)
  • const auto& 只讀引用(默認選擇),auto& 可修改引用,auto 值拷貝。
  • 適用於任何有 begin() / end() 的類型(STL 容器、初始值列表等)。
  • 遍歷過程中不能增刪容器元素,會導致迭代器失效。

工程拓展

在 ROS2 中,遍歷傳感器數據列表、遍歷節點列表等場景頻繁使用範圍 for。在 Boost.Asio 中,也常用範圍 for 遍歷連接列表。掌握了 const auto& 這個慣用法,你能寫出和優秀開源項目風格一致的代碼。

音乐页