名稱空間
變體
操作

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

來自 cppreference.com
< cpp‎ | 語言
 
 
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)
儲存期說明符
初始化
表示式
替代表示
字面量
布林字面量 - 整數字面量 - 浮點字面量
字元字面量 - 字串字面量 - nullptr (C++11)
使用者定義 (C++11)
工具
屬性 (C++11)
型別
typedef 宣告
類型別名宣告 (C++11)
型別轉換
記憶體分配
類特有的函式屬性
explicit (C++11)
static

特殊成員函式
模板
雜項
 
 

對範圍進行 for 迴圈。

用作傳統 for 迴圈的更具可讀性的替代品,用於遍歷值範圍,例如容器中的所有元素。

目錄

[編輯] 語法

attr (可選) for ( init-statement (可選) item-declaration : range-initializer ) statement
屬性 - 任意數量的屬性
init-statement - (C++20 起) 之一
(C++23 起)

注意,任何 init-statement 都必須以分號結尾。這就是為什麼它通常被非正式地描述為表示式或聲明後跟分號的原因。

item-declaration - 每個範圍項的宣告
range-initializer - 表示式花括號初始化列表
語句 - 任何語句 (通常是複合語句)

[編輯] 解釋

上述語法生成的程式碼等價於以下程式碼 除了 range-initializer 的臨時物件的生命週期擴充套件 (參見下文)(C++23 起) (包裹在 /* */ 中的變數和表示式僅用於說明)

{

auto&& /* range */ = range-initializer ;
for (auto /* begin */ = /* begin-expr */, /* end */ = /* end-expr */;
/* begin */ != /* end */; ++/* begin */)
{
item-declaration = */* begin */;
語句
}

}

(C++17 前)

{

auto&& /* range */ = range-initializer ;
auto /* begin */ = /* begin-expr */;
auto /* end */ = /* end-expr */;
for ( ; /* begin */ != /* end */; ++/* begin */)
{
item-declaration = */* begin */;
語句
}

}

(C++17 起)
(C++20 前)

{

init-statement
auto&& /* range */ = range-initializer ;
auto /* begin */ = /* begin-expr */;
auto /* end */ = /* end-expr */;
for ( ; /* begin */ != /* end */; ++/* begin */)
{
item-declaration = */* begin */;
語句
}

}

(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 中所有臨時物件的生命週期

[編輯] 關鍵詞

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++ 標準。

缺陷報告 應用於 釋出時的行為 正確的行為
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”都存在時才使用
僅當兩者都存在時才使用

[編輯] 另請參閱

範圍中的元素應用一元函式物件
(函式模板) [編輯]