基於範圍的 for
迴圈 (C++11 起)
對範圍進行 for 迴圈。
用作傳統 for 迴圈的更具可讀性的替代品,用於遍歷值範圍,例如容器中的所有元素。
目錄 |
[編輯] 語法
attr (可選) for ( init-statement (可選) item-declaration : range-initializer ) statement |
|||||||||
屬性 | - | 任意數量的屬性 | ||
init-statement | - | (C++20 起) 之一
注意,任何 init-statement 都必須以分號結尾。這就是為什麼它通常被非正式地描述為表示式或聲明後跟分號的原因。 | ||
item-declaration | - | 每個範圍項的宣告 | ||
range-initializer | - | 表示式 或 花括號初始化列表 | ||
語句 | - | 任何語句 (通常是複合語句) |
[編輯] 解釋
上述語法生成的程式碼等價於以下程式碼 除了 range-initializer 的臨時物件的生命週期擴充套件 (參見下文)(C++23 起) (包裹在 /* */ 中的變數和表示式僅用於說明)
|
(C++17 前) |
|
(C++17 起) (C++20 前) |
|
(C++20 起) |
range-initializer 被求值以初始化要迭代的序列或範圍。序列的每個元素依次被解引用並用於初始化具有 item-declaration 中給定的型別和名稱的變數。
item-declaration 可以是以下之一
僅用於說明的表示式 /* begin-expr */ 和 /* end-expr */ 定義如下
- 如果 /* range */ 的型別是對陣列型別
R
的引用
- 如果
R
具有界限 N,則 /* begin-expr */ 為 /* range */,/* end-expr */ 為 /* range */ + N。 - 如果
R
是未知界限陣列或不完整型別陣列,則程式格式錯誤。
- 如果
- 如果 /* range */ 的型別是對類型別
C
的引用,並且在C
的作用域中搜索名稱“begin
”和“end
”都至少找到一個宣告,則 /* begin-expr */ 為 /* range */.begin(),/* end-expr */ 為 /* range */.end()。 - 否則,/* begin-expr */ 為 begin(/* range */),/* end-expr */ 為 end(/* range */),其中“
begin
”和“end
”透過實參依賴查詢(不執行非 ADL 查詢)找到。
如果迴圈需要在 statement 內終止,可以使用 break 語句作為終止語句。
如果當前迭代需要在 statement 內終止,可以使用 continue 語句作為快捷方式。
如果在 init-statement 中引入的名稱在 statement 的最外層塊中被重新宣告,則程式格式錯誤
for (int i : {1, 2, 3}) int i = 1; // error: redeclaration
[編輯] 臨時範圍初始化器
如果 range-initializer 返回一個臨時物件,其生命週期會延長到迴圈結束,如繫結到轉發引用 /* range */ 所示。
range-initializer 中所有臨時物件的生命週期不會延長,除非它們本來會在 range-initializer 結束時銷燬(C++23 起)。
// if foo() returns by value for (auto& x : foo().items()) { /* ... */ } // until C++23 undefined behavior
此問題可透過使用 init-statement 解決 for (T thing = foo(); auto& x : thing.items()) { /* ... */ } // OK |
(C++20 起) |
請注意,即使在 C++23 中,中間函式呼叫的非引用引數也不會獲得生命週期擴充套件(因為在某些 ABI 中,它們在被呼叫方而不是呼叫方中銷燬),但這僅對本身存在 bug 的函式而言是個問題。 using T = std::list<int>; const T& f1(const T& t) { return t; } const T& f2(T t) { return t; } // always returns a dangling reference T g(); void foo() { for (auto e : f1(g())) {} // OK: lifetime of return value of g() extended for (auto e : f2(g())) {} // UB: lifetime of f2's value parameter ends early } |
(C++23 起) |
[編輯] 注意
如果 range-initializer 是花括號初始化列表,則 /* range */ 會被推導為對 std::initializer_list 的引用。
在通用程式碼中,使用轉發引用推導是安全的,實際上是更推薦的,例如 for (auto&& var : sequence)。
如果範圍型別具有名為“begin
”和“end
”的成員,則使用成員解釋。無論該成員是型別、資料成員、函式還是列舉器,也無論其可訪問性如何。因此,像 class meow { enum { begin = 1, end = 2 }; /* rest of class */ }; 這樣的類不能與基於範圍的 for 迴圈一起使用,即使存在名稱空間作用域的“begin
”/“end
”函式。
雖然在 item-declaration 中宣告的變數通常在 statement 中使用,但這並非必需。
截至 C++17,/* begin-expr */ 和 /* end-expr */ 的型別不必相同,實際上 /* end-expr */ 的型別不必是迭代器:它只需要能夠與其中一個進行不等比較。這使得可以透過謂詞(例如“迭代器指向空字元”)來界定範圍。 |
(C++17 起) |
當與具有寫時複製語義的(非 const)物件一起使用時,基於範圍的 for 迴圈可能會透過(隱式)呼叫非 const begin()
成員函式觸發深層複製。
如果這是不希望的(例如,因為迴圈實際上並未修改物件),可以透過使用 std::as_const 來避免 struct cow_string { /* ... */ }; // a copy-on-write string cow_string str = /* ... */; // for (auto x : str) { /* ... */ } // may cause deep copy for (auto x : std::as_const(str)) { /* ... */ } |
(C++17 起) |
功能測試宏 | 值 | 標準 | 特性 |
---|---|---|---|
__cpp_range_based_for |
200907L |
(C++11) | 基於範圍的 for 迴圈 |
201603L |
(C++17) | 基於範圍的 for 迴圈,帶有不同 begin /end 型別 | |
202211L |
(C++23) | 延長 range-initializer 中所有臨時物件的生命週期 |
[編輯] 關鍵詞
[編輯] 示例
#include <iostream> #include <vector> int main() { std::vector<int> v = {0, 1, 2, 3, 4, 5}; for (const int& i : v) // access by const reference std::cout << i << ' '; std::cout << '\n'; for (auto i : v) // access by value, the type of i is int std::cout << i << ' '; std::cout << '\n'; for (auto&& i : v) // access by forwarding reference, the type of i is int& std::cout << i << ' '; std::cout << '\n'; const auto& cv = v; for (auto&& i : cv) // access by f-d reference, the type of i is const int& std::cout << i << ' '; std::cout << '\n'; for (int n : {0, 1, 2, 3, 4, 5}) // the initializer may be a // braced-enclosed initializer list std::cout << n << ' '; std::cout << '\n'; int a[] = {0, 1, 2, 3, 4, 5}; for (int n : a) // the initializer may be an array std::cout << n << ' '; std::cout << '\n'; for ([[maybe_unused]] int n : a) std::cout << 1 << ' '; // the loop variable need not be used std::cout << '\n'; for (auto n = v.size(); auto i : v) // the init-statement (C++20) std::cout << --n + i << ' '; std::cout << '\n'; for (typedef decltype(v)::value_type elem_t; elem_t i : v) // typedef declaration as init-statement (C++20) std::cout << i << ' '; std::cout << '\n'; for (using elem_t = decltype(v)::value_type; elem_t i : v) // alias declaration as init-statement (C++23) std::cout << i << ' '; std::cout << '\n'; }
輸出
0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5 1 1 1 1 1 1 5 5 5 5 5 5 0 1 2 3 4 5 0 1 2 3 4 5
[編輯] 缺陷報告
下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。
缺陷報告 | 應用於 | 釋出時的行為 | 正確的行為 |
---|---|---|---|
CWG 1442 | C++11 | 未指明非成員 “ begin ”和“end ”的查詢是否包含通常的非限定查詢 |
不包含通常的非限定查詢 |
CWG 2220 | C++11 | 在 init-statement 中引入的名稱可以被重新宣告 | 在這種情況下程式格式錯誤 |
CWG 2825 | C++11 | 如果 range-initializer 是一個花括號初始化列表, 將查詢非成員“ begin ”和“end ” |
在這種情況下將查詢成員“begin ”和“ end ” |
P0962R1 | C++11 | 如果存在成員“begin ”和“end ”,則使用成員解釋成員“ begin ”和“end ”都存在時才使用 |
僅當兩者都存在時才使用 |
[編輯] 另請參閱
對範圍中的元素應用一元函式物件 (函式模板) |