第 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 | 要訪問 i 和 i - 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 | 最基礎的範圍 for | for (int n : v) | 直接讀懂意圖:"對 v 中每個元素 n" | 這種寫法會拷貝每個 int,但 int 很小無所謂 |
| 示例 2 | const auto& vs auto& | const auto&、auto& | const auto& 避免拷貝;auto& 可修改元素 | 遍歷 string 等大對象時一定要用引用 |
| 示例 3 | 遍歷初始值列表和 map | 遍歷 {...} 和 map | 初始值列表可以直接放在 for 裡 | map 遍歷出來的是 std::pair |
| 示例 4 | 結構化綁定解包 map | for (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 循環可能更合適,或者用枚舉技巧。
使用建議
- 不修改元素:用
const auto&— 這是默認選擇。 - 修改元素:用
auto&— 明確意圖是修改。 - 基本類型(int/double)且不修改:可以用
auto— 拷貝開銷可忽略。 - 需要下標時老老實實用傳統 for — 範圍 for 不適合所有場景。
- 遍歷 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& 這個慣用法,你能寫出和優秀開源項目風格一致的代碼。