第 20.6 節

結構化綁定

0瀏覽次數0訪問次數--跳出率--平均停留

本節解決什麼問題

當函數需要返回多個值(如成功標誌 + 數據),或者遍歷 map 時需要同時拿到 key 和 value,在 C++17 之前需要藉助 std::pair / std::tuple + std::get<>().first / .second,代碼冗長且不易讀。

結構化綁定讓你可以一行語句把複合類型(pair、tuple、結構體)分解成獨立的變量,代碼清晰很多。

這個特性是什麼

結構化綁定(Structured Bindings)是 C++17 引入的語法,允許你將一個結構體、pair、tuple 或數組的元素"綁定"到獨立的變量名上。

C++ 標準版本

C++17。編譯本節完整示例時,需要啓用 C++17 或更高標準,例如:

g++ example.cpp -std=c++17

需要的頭文件

不需要額外頭文件。結構化綁定是語言特性。如果用於 tuple 需要 <tuple>,用於 pair 需要 <utility>

從 C++17 前的寫法到結構化綁定

C++17 之前沒有統一的“解包”語法。面對不同的複合類型,需要使用各自的訪問方式:

類型C++17 之前的訪問方式C++17 結構化綁定
std::pair.first.secondauto [first, second] = pair;
std::tuplestd::get<0>()std::get<1>()auto [a, b, ...] = tuple;
結構體.成员名auto [a, b, ...] = object;
數組[0][1] 等下標auto [a, b, ...] = array;

std::pair 為例,C++17 之前需要分別訪問 firstsecond

std::pair<int, bool> result = {5, true};

// C++17 之前:通过 pair 固定的成员名 first 和 second 取出两个值。
int value = result.first;
bool ok = result.second;

C++17 開始,可以使用結構化綁定一次取出兩個值,並根據含義為它們命名:

std::pair<int, bool> result = {5, true};

// C++17:value 对应 result.first,ok 对应 result.second。
auto [value, ok] = result;

結構化綁定沒有改變 pairtuple、結構體或數組本身,只是提供了一套統一、簡潔的取值語法。方括號中的變量按元素或成員的原有順序一一對應。

基本語法

// 以下三种写法都是 C++17 开始支持的结构化绑定。
auto [变量1, 变量2, ...] = 表达式;        // 值绑定:通常会得到各元素的副本。
auto& [变量1, 变量2, ...] = 表达式;       // 引用绑定:可以通过变量修改原对象。
const auto& [变量1, 变量2, ...] = 表达式;  // 只读引用绑定:不拷贝,也不允许修改。

常用用法

下表中的寫法都需要啓用 C++17 或更高版本:

用法説明
auto [x, y] = pair解包 pair
auto [a, b, c] = std::tuple<...>(...)解包 tuple
auto [x, y, z] = struct_obj解包結構體(按成員順序)
for (auto& [k, v] : map)遍歷 map 時解包 key/value
auto [it, ok] = map.insert(...)解包 insert 返回的 pair<iterator, bool>

示例代碼

示例 1:函數返回 pair——從 C++17 前的成員訪問到結構化綁定

#include <iostream>
#include <utility>  // for std::pair

// 返回两个值的函数
std::pair<int, bool> divide(int a, int b)
{
    if (b == 0)
    {
        return {0, false};  // 除数为 0,返回失败
    }
    return {a / b, true};   // 返回结果和成功标志
}

int main()
{
    // C++17 之前:先接收完整的 pair 对象。
    const std::pair<int, bool> old_result = divide(10, 2);

    // pair 的 first 是第一个值,这里代表除法结果。
    const int old_value = old_result.first;

    // pair 的 second 是第二个值,这里代表操作是否成功。
    const bool old_ok = old_result.second;

    if (old_ok)
    {
        std::cout << "before C++17: 10 / 2 = " << old_value << "\n";
    }

    // C++17:结构化绑定把返回的 pair 按顺序拆成 value 和 ok。
    // value 对应 pair.first,ok 对应 pair.second。
    const auto [value, ok] = divide(10, 2);
    if (ok)
    {
        std::cout << "C++17: 10 / 2 = " << value << "\n";
    }

    // C++17:同样的方法也可以接收失败结果。
    const auto [failed_value, failed_ok] = divide(10, 0);

    // failed_ok 为 false,说明 failed_value 不是有效的除法结果。
    if (!failed_ok)
    {
        std::cout << "10 / 0: division failed\n";
    }

    return 0;
}

運行結果

before C++17: 10 / 2 = 5
C++17: 10 / 2 = 5
10 / 0: division failed

示例 2:tuple 和數組——從逐項訪問到 C++17 結構化綁定

#include <iostream>
#include <string>
#include <tuple>

// 返回多个值
std::tuple<std::string, int, double> get_student_info()
{
    return {"Alice", 20, 3.8};  // 姓名、年龄、GPA
}

int main()
{
    // C++17 之前:先保存完整的 tuple。
    const auto old_info = get_student_info();

    // std::get<下标>() 按位置访问 tuple,编号从 0 开始。
    const std::string& old_name = std::get<0>(old_info);
    const int old_age = std::get<1>(old_info);
    const double old_gpa = std::get<2>(old_info);

    std::cout << "before C++17 tuple: "
              << old_name << ", " << old_age << ", " << old_gpa << "\n";

    // C++17:结构化绑定按 tuple 中元素的顺序声明三个变量。
    // const auto& 表示只读引用,可以避免复制 tuple 中的 string。
    const auto& [name, age, gpa] = old_info;
    std::cout << "C++17 tuple: "
              << name << ", " << age << ", " << gpa << "\n";

    int numbers[] = {10, 20, 30};

    // C++17 之前:使用下标逐个访问数组元素。
    std::cout << "before C++17 array: "
              << numbers[0] << ", " << numbers[1] << ", " << numbers[2] << "\n";

    // C++17:数组有三个元素,所以左侧也必须声明三个名字。
    // 这里没有写 &,因此 a、b、c 是三个元素的副本。
    const auto [a, b, c] = numbers;
    std::cout << "C++17 array: " << a << ", " << b << ", " << c << "\n";

    return 0;
}

運行結果

before C++17 tuple: Alice, 20, 3.8
C++17 tuple: Alice, 20, 3.8
before C++17 array: 10, 20, 30
C++17 array: 10, 20, 30

示例 3:結構體——從成員訪問到 C++17 結構化綁定

#include <iostream>
#include <string>

struct Point
{
    double x;
    double y;
    double z;
};

Point midpoint(const Point& p1, const Point& p2)
{
    return {
        (p1.x + p2.x) / 2.0,
        (p1.y + p2.y) / 2.0,
        (p1.z + p2.z) / 2.0
    };
}

int main()
{
    Point p1{1.0, 2.0, 3.0};
    Point p2{5.0, 6.0, 7.0};

    // C++17 之前:先接收完整的结构体,再通过成员名访问数据。
    const Point old_middle = midpoint(p1, p2);
    std::cout << "before C++17 midpoint: ("
              << old_middle.x << ", "
              << old_middle.y << ", "
              << old_middle.z << ")\n";

    // C++17:结构化绑定按成员的声明顺序解包结构体。
    // x 对应 Point::x,y 对应 Point::y,z 对应 Point::z。
    const auto [x, y, z] = midpoint(p1, p2);
    std::cout << "C++17 midpoint: (" << x << ", " << y << ", " << z << ")\n";

    // C++17:也可以直接解包临时创建的结构体对象。
    const auto [a, b, c] = Point{10.0, 20.0, 30.0};
    std::cout << "C++17 temporary point: ("
              << a << ", " << b << ", " << c << ")\n";

    return 0;
}

運行結果

before C++17 midpoint: (3, 4, 5)
C++17 midpoint: (3, 4, 5)
C++17 temporary point: (10, 20, 30)

示例 4:map——對比 C++17 前後的插入結果和遍歷寫法

#include <iostream>
#include <map>
#include <string>

int main()
{
    std::map<std::string, int> scores;

    // map::insert 返回一个 pair<iterator, bool>:
    // first 是指向相应元素的迭代器,second 表示是否插入成功。

    // C++17 之前:先接收完整的 pair,再分别读取 first 和 second。
    const auto old_insert_result = scores.insert({"Alice", 85});
    const auto old_it = old_insert_result.first;
    const bool old_inserted = old_insert_result.second;

    if (old_inserted)
    {
        // 迭代器的 second 是 map 元素的 value,这里是 Alice 的分数。
        std::cout << "before C++17 insert: Alice inserted, score: "
                  << old_it->second << "\n";
    }

    // C++17:直接把 insert 返回的 pair 解包为迭代器和成功标志。
    // 由于 Alice 已经存在,这次重复插入会失败。
    const auto [it, inserted] = scores.insert({"Alice", 100});
    if (!inserted)
    {
        std::cout << "C++17 insert: Alice already exists, score: "
                  << it->second << "\n";
    }

    // 添加另外两个元素,供下面演示遍历使用。
    scores.insert({"Bob", 92});
    scores.insert({"Charlie", 78});

    // C++17 之前:范围 for 每次取得一个 pair。
    // pair.first 是 map 的 key,pair.second 是 map 的 value。
    std::cout << "\nbefore C++17 traversal:\n";
    for (const auto& pair : scores)
    {
        std::cout << "  " << pair.first << ": " << pair.second << "\n";
    }

    // C++17:在范围 for 中使用结构化绑定,直接命名 key 和 value。
    // const auto& 表示只读引用,既不复制元素,也不会修改 map。
    std::cout << "C++17 traversal:\n";
    for (const auto& [name, score] : scores)
    {
        std::cout << "  " << name << ": " << score << "\n";
    }

    return 0;
}

運行結果

before C++17 insert: Alice inserted, score: 85
C++17 insert: Alice already exists, score: 85

before C++17 traversal:
  Alice: 85
  Bob: 92
  Charlie: 78
C++17 traversal:
  Alice: 85
  Bob: 92
  Charlie: 78

運行結果

見上方每個示例的"運行結果"。

示例中的關鍵語法解釋

示例講了什麼新出現的語法為什麼這樣寫注意事項
示例 1pair.first/second 改為解包 pairauto [value, ok] = pairC++17 可以一次聲明兩個有意義的變量名變量名可以自定義,但對應順序不能改變
示例 2std::get<N>()、數組下標改為統一解包auto [a,b,c] = tupleauto [a,b,c] = arrayC++17 對 tuple 和數組採用相似語法變量個數必須和 tuple、數組的元素個數一致
示例 3.成员名 改為解包結構體auto [x,y,z] = struct_objC++17 按結構體成員聲明順序綁定可綁定的成員需要滿足訪問條件,且順序由結構體定義決定
示例 4對比 C++17 前後的 map 寫法auto [it, ok] = m.insert(...)[name, score]返回值和 map 元素都能直接取出並命名insert 返回 pair<iterator, bool>

auto [a, b]auto& [a, b] 的區別

下面討論的 auto [a, b]auto& [a, b] 都是 C++17 結構化綁定語法。結構化綁定不寫 & 時通常會產生副本;遍歷大對象或想修改原數據時,需要考慮使用引用。

C++17 之前沒有結構化綁定。修改 map 的 value 時,通常取得整個 pair 的引用,再修改它的 second

// C++17 之前:pair.second 是 map 元素的 value。
for (auto& pair : scores)
{
    pair.second += 10;
}

示例 5:C++17 結構化綁定中,修改 map 的 value 必須使用引用

#include <iostream>
#include <map>
#include <string>

int main()
{
    std::map<std::string, int> scores = {
        {"Alice", 85},
        {"Bob", 92}
    };

    // C++17:没有写 &,name 和 score 不引用 map 中的原元素。
    // 每轮循环得到的是当前 key 和 value 的副本。
    for (auto [name, score] : scores)
    {
        // 这里只修改局部变量 score,scores 中的原分数不会变化。
        score += 10;
    }

    std::cout << "after auto copy:\n";

    // C++17:const auto& 不复制 map 元素,适合只读输出。
    for (const auto& [name, score] : scores)
    {
        std::cout << name << ": " << score << "\n";
    }

    // C++17:auto& 让 name 和 score 引用 map 中的原元素。
    for (auto& [name, score] : scores)
    {
        // score 是 value 的引用,因此这次会修改原 map 中的分数。
        score += 10;
    }

    std::cout << "after auto& reference:\n";

    // 修改完成后再次只读遍历,确认新分数已经保存在 map 中。
    for (const auto& [name, score] : scores)
    {
        std::cout << name << ": " << score << "\n";
    }

    return 0;
}

運行結果

after auto copy:
Alice: 85
Bob: 92
after auto& reference:
Alice: 95
Bob: 102

注意:map 的 key 仍然不能改。即使使用 C++17 的 auto& [name, score]name 也是隻讀的,因為 map 必須保證 key 不變,才能維持內部的排序規則。

常見錯誤

錯誤 1:變量個數不匹配

// C++17:右侧 tuple 有三个元素,左侧却只有两个变量,无法一一对应。
auto [x, y] = std::make_tuple(1, 2, 3);  // ❌ 编译错误

正確做法:變量個數必須等於複合類型的元素個數。

錯誤 2:解包非公有成員的結構體

struct A { private: int x; public: int y; };
A obj;

// C++17:此处不能访问私有成员 x,因此不能这样进行结构化绑定。
auto [x, y] = obj;  // ❌ 编译错误

正確做法:在使用結構化綁定的位置,所有被綁定的成員都必須可以訪問。對初學階段常見的普通結構體來説,最直觀的做法是讓這些成員保持 public

錯誤 3:用 auto 而不是 auto& 導致修改無效

// C++17:没有写 &,k 和 v 是从 map 元素得到的副本。
auto [k, v] = *map.begin();

// 这里只修改局部变量 v,不会修改 map 中的 value。
v = 100;

正確做法:要修改原值用 auto& [k, v] = ...

使用建議

  1. 函數返回多值時優先用 std::tuple/std::pair + 結構化綁定,而不是輸出參數。
  2. C++17 中只讀遍歷 map 時優先用 const auto& [key, value],避免拷貝,也能清楚表達 key 和 value 的含義。
  3. 解包 insert 返回值:C++17 的 auto [it, inserted] = map.insert(...) 比繼續使用 .first.second 更直觀。
  4. 需要修改原數據時使用 auto&;只讀且不想拷貝時使用 const auto&
  5. 維護 C++17 以前的項目時繼續使用原有訪問方式:pair 用 .first/.second,tuple 用 std::get<N>()

小結

  • C++17 之前需要通過 .first/.secondstd::get<N>()、成員名或數組下標分別取值。
  • C++17 結構化綁定可以把 pair / tuple / 結構體 / 數組分解成獨立變量。
  • C++17 基本語法是 auto [var1, var2, ...] = expression
  • 遍歷 map 時 [key, value] 是最佳實踐(C++17)。
  • insert 返回值用 [iterator, bool] 解包非常清晰。
音乐页