命名空間
變體
動作

基於範圍的 for 迴圈 (C++11 起)

出自 cppreference.com
< cpp‎ | language
 
 
C++ 語言
一般主題
流程控制
條件執行陳述式
if
疊代陳述式 (迴圈)
for
基於範圍的 for 迴圈 (C++11)
跳躍陳述式
函式
函式宣告
Lambda 函式運算式
inline 指定符
動態例外規範 (直到 C++17*)
noexcept 指定符 (C++11)
例外
命名空間
型別
指定符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
儲存期指定符
初始化
 
 

在一個範圍上執行 for 迴圈。

作為傳統 for 迴圈的一種更具可讀性的等效寫法,用於操作一系列數值,例如容器中的所有元素。

目錄

[編輯] 語法

屬性 (可選) for ( 初始化語句 (可選) 項目宣告 : 範圍初始化器 ) 語句
屬性 - 任意數量的 屬性
初始化陳述式 (init-statement) - (C++20 起) 下列之一
(自 C++23 起)

請注意,任何 init-statement 都必須以分號結尾。這就是為什麼它通常被非正式地描述為一個表達式或宣告,後面跟著一個分號。

項目宣告 - 用於每個範圍項目的宣告
範圍初始化器 - 一個 表達式大括號包圍的初始化器列表
statement - 任意 語句(通常為複合語句)

[編輯] 解釋

上述語法產生的程式碼與以下內容等效 (除了 範圍初始化器 的暫時對象生命週期擴展之外,請參見下文(C++23 起)(以 /* */ 包裹的變數和表達式僅供說明使用)

{

auto&& /* range */ = 範圍初始化器 ;
for (auto /* begin */ = /* begin-expr */, /* end */ = /* end-expr */;
/* begin */ != /* end */; ++/* begin */)
{
項目宣告 = */* begin */;
statement
}

}

(直到 C++17)

{

auto&& /* range */ = 範圍初始化器 ;
auto /* begin */ = /* begin-expr */;
auto /* end */ = /* end-expr */;
for ( ; /* begin */ != /* end */; ++/* begin */)
{
項目宣告 = */* begin */;
statement
}

}

(自 C++17 起)
(直到 C++20)

{

初始化陳述式 (init-statement)
auto&& /* range */ = 範圍初始化器 ;
auto /* begin */ = /* begin-expr */;
auto /* end */ = /* end-expr */;
for ( ; /* begin */ != /* end */; ++/* begin */)
{
項目宣告 = */* begin */;
statement
}

}

(自 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) 範圍初始化器 中所有暫時對象的生命週期延長

[編輯] 關鍵字

for

[編輯] 範例

#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” 存在時
僅在兩者都存在時才使用

[編輯] 參閱

將一元函式物件套用於範圍中的元素
(函式模板) [編輯]
English Deutsch 日本語 中文(简体) 中文(繁體)