基於範圍的 for 迴圈 (C++11 起)
在一個範圍上執行 for 迴圈。
作為傳統 for 迴圈的一種更具可讀性的等效寫法,用於操作一系列數值,例如容器中的所有元素。
目錄 |
[編輯] 語法
屬性 (可選) for ( 初始化語句 (可選) 項目宣告 : 範圍初始化器 ) 語句 |
|||||||||
| 屬性 | - | 任意數量的 屬性 | ||
| 初始化陳述式 (init-statement) | - | (C++20 起) 下列之一
請注意,任何 init-statement 都必須以分號結尾。這就是為什麼它通常被非正式地描述為一個表達式或宣告,後面跟著一個分號。 | ||
| 項目宣告 | - | 用於每個範圍項目的宣告 | ||
| 範圍初始化器 | - | 一個 表達式 或 大括號包圍的初始化器列表 | ||
| statement | - | 任意 語句(通常為複合語句) |
[編輯] 解釋
上述語法產生的程式碼與以下內容等效 (除了 範圍初始化器 的暫時對象生命週期擴展之外,請參見下文)(C++23 起)(以 /* */ 包裹的變數和表達式僅供說明使用)
|
|
(直到 C++17) |
|
|
(自 C++17 起) (直到 C++20) |
|
|
(自 C++20 起) |
範圍初始化器 被求值以初始化要迭代的序列或範圍。序列中的每個元素依次被解引用,並用於初始化類型和名稱由 項目宣告 給出的變數。
項目宣告 可以是下列之一
僅供說明用的表達式 /* begin-expr */ 和 /* end-expr */ 定義如下
- 如果 /* range */ 的類型是一個陣列類型
R的參考
- 如果
R的邊界為 N,則 /* begin-expr */ 為 /* range */,而 /* end-expr */ 為 /* range */ + N。 - 如果
R是未知邊界的陣列或不完整類型的陣列,則該程式為格式錯誤 (ill-formed)。
- 如果
- 如果 /* range */ 的類型是類別類型
C的參考,且在C的作用域中搜尋名稱 “begin” 和 “end” 各自至少找到一個宣告,則 /* begin-expr */ 為 /* range */.begin(),且 /* end-expr */ 為 /* range */.end()。 - 否則,/* begin-expr */ 為 begin(/* range */),且 /* end-expr */ 為 end(/* range */),其中 “
begin” 和 “end” 是透過 參數依賴查找 (ADL) 找到的(不會執行非 ADL 查找)。
若需要在 statement 內部終止迴圈,可以使用 break 語句作為終止語句。
若需要在 statement 內部終止當前迭代,可以使用 continue 語句作為捷徑。
如果 初始化語句 中引入的名稱在 語句 的最外層區塊中被重新宣告,則該程式為格式錯誤。
for (int i : {1, 2, 3}) int i = 1; // error: redeclaration
[編輯] 暫時範圍初始化器
如果 範圍初始化器 返回一個暫時對象,其生命週期會延長至迴圈結束,如綁定到轉發參考 /* range */ 所指示。
範圍初始化器 內的所有暫時對象生命週期不會被延長,除非它們本來就會在 範圍初始化器 結尾處被銷毀(C++23 起)。
// if foo() returns by value for (auto& x : foo().items()) { /* ... */ } // until C++23 undefined behavior
|
此問題可以使用 初始化語句 來解決。 for (T thing = foo(); auto& x : thing.items()) { /* ... */ } // OK |
(自 C++20 起) |
|
請注意,即使在 C++23 中,中間函數調用的非參考參數也不會獲得生命週期延長(因為在某些 ABI 中它們是在被調用者銷毀,而非在調用者),但這僅對已經存在缺陷的函數構成問題。 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 */ 會被推導為對 std::initializer_list 的參考。
在泛型程式碼中使用推導到轉發參考 for (auto&& var : sequence) 是安全的,事實上也是更佳的作法。
如果範圍類型有名為 “begin” 和 “end” 的成員,則會使用成員解釋方式。這是在不考慮成員是類型、資料成員、函數還是列舉項,也不考慮其存取權限的情況下進行的。因此,像 class meow { enum { begin = 1, end = 2 }; /* ... */ }; 這樣的類別即使存在命名空間作用域的 “begin”/“end” 函數,也無法與基於範圍的 for 迴圈一起使用。
雖然 項目宣告 中宣告的變數通常會在 語句 中使用,但這並非強制要求。
|
從 C++17 開始,/* begin-expr */ 和 /* end-expr */ 的類型不必相同,實際上 /* end-expr */ 的類型也不必是迭代器:它只需要能與前者進行不等比較即可。這使得透過斷言來界定範圍成為可能(例如“迭代器指向空字元”)。 |
(自 C++17 起) |
當與具有寫時複製 (copy-on-write) 語義的(非 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) | 帶有 不同 begin/end 類型 的基於範圍的 for 迴圈 | |
202211L |
(C++23) | 範圍初始化器 中所有暫時對象的生命週期延長 |
[編輯] 關鍵字
[編輯] 範例
#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++ 標準。
| DR | 應用於 | 出版時的行為 | 正確的行為 |
|---|---|---|---|
| CWG 1442 | C++11 | 非成員 “begin” 和 “end” 的查找是否包含通常的非受限查找是不明確的 |
無通常的非受限查找 |
| CWG 2220 | C++11 | 初始化語句 中引入的名稱可能被重新宣告 | 在這種情況下,程式為格式錯誤 |
| CWG 2825 | C++11 | 如果 範圍初始化器 是一個大括號包圍的初始化器列表, 會查找非成員 “ begin” 和 “end” |
會查找成員 “begin”以及此種情況下的 “ end” |
| P0962R1 | C++11 | 成員解釋方式用於 成員 “ begin” 和 “end” 存在時 |
僅在兩者都存在時才使用 |
[編輯] 參閱
| 將一元函式物件套用於範圍中的元素 (函式模板) |