C++17 结构化绑定

2026年7月12日 Crystal-Sky 11 min read C++C++17基础语法结构化绑定

学习目标

完成本节学习后,应能够:

  1. 理解结构化绑定的作用和基本语法;
  2. 使用结构化绑定拆分数组、结构体、pairtuple
  3. 正确区分值绑定、引用绑定和常量引用绑定;
  4. 在范围 for 循环和 STL 容器中使用结构化绑定;
  5. 识别结构化绑定中的常见错误。

什么是结构化绑定

结构化绑定(Structured Bindings)是 C++17 引入的一种语法。

它允许程序员将一个由多个元素组成的对象拆分为若干个变量,从而减少使用 firstsecondget<0>() 等写法。

传统写法:

std::pair<int, int> point = {10, 20};

int x = point.first;
int y = point.second;

使用结构化绑定:

std::pair<int, int> point = {10, 20};

auto [x, y] = point;

此时:

  • x 的值为 10;
  • y 的值为 20。

基本语法

结构化绑定的一般形式如下:

auto [变量1, 变量2, ...] = 表达式;

也可以使用引用或常量引用:

auto [a, b] = object;          // 值绑定
auto& [a, b] = object;         // 引用绑定
const auto& [a, b] = object;   // 常量引用绑定

变量数量必须匹配

结构化绑定左侧变量的数量必须与对象中可拆分元素的数量一致。

正确示例:

std::pair<int, int> p = {1, 2};
auto [x, y] = p;

错误示例:

std::pair<int, int> p = {1, 2};
auto [x] = p;       // 错误:pair 中有两个元素

绑定数组

结构化绑定可以拆分普通数组。

#include <iostream>
using namespace std;

int main() {
    int arr[3] = {10, 20, 30};

    auto [a, b, c] = arr;

    cout << a << ' ' << b << ' ' << c << '\n';
    return 0;
}

输出结果:

10 20 30

这里的 abc 是数组元素的副本。

修改这些变量不会改变原数组。

int arr[3] = {10, 20, 30};

auto [a, b, c] = arr;
a = 100;

cout << arr[0] << '\n';  // 输出 10

如果希望通过绑定变量修改原数组,应使用引用绑定:

int arr[3] = {10, 20, 30};

auto& [a, b, c] = arr;
a = 100;

cout << arr[0] << '\n';  // 输出 100

绑定结构体

结构化绑定可以拆分成员均为公开成员的简单结构体。

#include <iostream>
#include <string>
using namespace std;

struct Student {
    string name;
    int age;
    double score;
};

int main() {
    Student stu{"Alice", 20, 95.5};

    auto [name, age, score] = stu;

    cout << name << '\n';
    cout << age << '\n';
    cout << score << '\n';

    return 0;
}

结构体成员的绑定顺序与成员的声明顺序一致:

struct Student {
    string name;   // 第一个
    int age;       // 第二个
    double score;  // 第三个
};

因此:

auto [name, age, score] = stu;

分别对应 stu.namestu.agestu.score

例如:

auto [a, b, c] = stu;

其中:

  • a 对应 name
  • b 对应 age
  • c 对应 score

绑定 std::pair

std::pair 中包含两个元素,分别是 firstsecond

传统写法:

std::pair<std::string, int> student = {"Tom", 18};

std::string name = student.first;
int age = student.second;

结构化绑定写法:

std::pair<std::string, int> student = {"Tom", 18};

auto [name, age] = student;

完整示例:

#include <iostream>
#include <string>
#include <utility>
using namespace std;

int main() {
    pair<string, int> student = {"Tom", 18};

    auto [name, age] = student;

    cout << "姓名:" << name << '\n';
    cout << "年龄:" << age << '\n';

    return 0;
}

绑定 std::tuple

std::tuple 可以存储任意数量、任意类型的元素。

传统写法:

std::tuple<std::string, int, double> stu{"Bob", 19, 88.5};

std::string name = std::get<0>(stu);
int age = std::get<1>(stu);
double score = std::get<2>(stu);

结构化绑定写法:

auto [name, age, score] = stu;

完整示例:

#include <iostream>
#include <string>
#include <tuple>
using namespace std;

int main() {
    tuple<string, int, double> stu{"Bob", 19, 88.5};

    auto [name, age, score] = stu;

    cout << name << ' ' << age << ' ' << score << '\n';

    return 0;
}

值绑定与引用绑定

结构化绑定中最重要的内容之一,是区分值绑定和引用绑定。

值绑定

语法:

auto [a, b] = object;

此时会根据原对象创建一份新的绑定对象。

通过绑定变量进行修改,通常不会影响原对象。

std::pair<int, int> p = {10, 20};

auto [x, y] = p;
x = 100;

cout << p.first << '\n';  // 输出 10

引用绑定

语法:

auto& [a, b] = object;

绑定变量直接引用原对象中的元素。

std::pair<int, int> p = {10, 20};

auto& [x, y] = p;
x = 100;

cout << p.first << '\n';  // 输出 100

常量引用绑定

语法:

const auto& [a, b] = object;

这种方式不会复制对象,同时禁止通过绑定变量修改元素。

std::pair<int, int> p = {10, 20};

const auto& [x, y] = p;

cout << x << ' ' << y << '\n';
// x = 100;  // 错误:x 是只读的

三种方式对比

写法是否复制能否修改原对象
auto [a,b]
auto& [a,b]
const auto& [a,b]

在函数返回值中使用

结构化绑定非常适合接收包含多个结果的函数返回值。

返回 pair

#include <iostream>
#include <utility>
using namespace std;

pair<int, int> divide(int a, int b) {
    return {a / b, a % b};
}

int main() {
    auto [quotient, remainder] = divide(17, 5);

    cout << "商:" << quotient << '\n';
    cout << "余数:" << remainder << '\n';

    return 0;
}

输出:

商:3
余数:2

返回 tuple

#include <iostream>
#include <tuple>
using namespace std;

tuple<int, int, double> analyze(int a, int b) {
    int sum = a + b;
    int product = a * b;
    double average = (a + b) / 2.0;

    return {sum, product, average};
}

int main() {
    auto [sum, product, average] = analyze(4, 6);

    cout << sum << '\n';
    cout << product << '\n';
    cout << average << '\n';

    return 0;
}

这种写法比使用多个输出参数更加直观。

在范围 for 循环中使用

结构化绑定经常与范围 for 循环结合使用。

遍历 map

传统写法:

for (const auto& item : mp) {
    cout << item.first << ' ' << item.second << '\n';
}

结构化绑定写法:

for (const auto& [key, value] : mp) {
    cout << key << ' ' << value << '\n';
}

完整示例:

#include <iostream>
#include <map>
#include <string>
using namespace std;

int main() {
    map<string, int> scores{
        {"Alice", 95},
        {"Bob", 88},
        {"Tom", 91}
    };

    for (const auto& [name, score] : scores) {
        cout << name << ": " << score << '\n';
    }

    return 0;
}

修改 map 中的值

如果需要修改容器中的值,应使用引用:

for (auto& [name, score] : scores) {
    score += 5;
}

此时每个学生的分数都会增加 5。

与集合插入操作结合

许多 STL 操作会返回 pair,结构化绑定可以使代码更清晰。

例如,set::insert 会返回:

pair<iterator, bool>

其中:

  • 第一个元素是指向对应元素的迭代器;
  • 第二个元素表示是否成功插入。

示例:

#include <iostream>
#include <set>
using namespace std;

int main() {
    set<int> numbers;

    auto [it1, success1] = numbers.insert(10);
    auto [it2, success2] = numbers.insert(10);

    cout << boolalpha;
    cout << success1 << '\n';  // true
    cout << success2 << '\n';  // false

    return 0;
}

这种写法比下面的传统形式更容易理解:

auto result = numbers.insert(10);

auto it = result.first;
bool success = result.second;

结构化绑定的作用域

结构化绑定变量与普通局部变量一样,受到作用域限制。

int main() {
    {
        std::pair<int, int> p = {1, 2};
        auto [x, y] = p;

        std::cout << x << ' ' << y << '\n';
    }

    // std::cout << x;  // 错误:x 已离开作用域
}

结构化绑定变量也不能与当前作用域内已有变量重名。

int x = 10;
std::pair<int, int> p = {1, 2};

auto [x, y] = p;  // 错误:x 重复定义

常见错误

忘记启用 C++17

结构化绑定属于 C++17 特性。

使用 GCC 或 Clang 编译时,应指定:

g++ main.cpp -std=c++17 -o main

如果未启用 C++17,可能出现类似错误:

structured bindings only available with -std=c++17

绑定变量数量不匹配

std::tuple<int, int, int> t{1, 2, 3};

auto [a, b] = t;  // 错误:tuple 中有三个元素

应改为:

auto [a, b, c] = t;

误以为值绑定会修改原对象

std::pair<int, int> p{1, 2};

auto [x, y] = p;
x = 100;

cout << p.first;  // 仍然是 1

需要修改原对象时应使用:

auto& [x, y] = p;

在循环中不必要地复制

下面的代码会在每次循环时复制元素:

for (auto [key, value] : mp) {
    cout << key << ' ' << value << '\n';
}

只读遍历时更推荐:

for (const auto& [key, value] : mp) {
    cout << key << ' ' << value << '\n';
}

尝试绑定私有成员

结构化绑定不能直接拆分具有不可访问成员的普通类对象。

class Student {
private:
    string name;
    int age;
};

直接写:

Student stu;
auto [name, age] = stu;

通常会因为成员不可访问而编译失败。

在基础教学中,可以先将结构化绑定用于简单结构体、数组、pairtuple

综合示例:学生成绩统计

下面的程序使用结构体、vector、结构化绑定和函数返回值,完成学生成绩统计。

#include <iostream>
#include <string>
#include <tuple>
#include <vector>
using namespace std;

struct Student {
    string name;
    int score;
};

tuple<int, int, double> analyze(const vector<Student>& students) {
    int maximum = students.front().score;
    int minimum = students.front().score;
    int sum = 0;

    for (const auto& [name, score] : students) {
        if (score > maximum) {
            maximum = score;
        }

        if (score < minimum) {
            minimum = score;
        }

        sum += score;
    }

    double average =
        static_cast<double>(sum) / students.size();

    return {maximum, minimum, average};
}

int main() {
    vector<Student> students{
        {"Alice", 95},
        {"Bob", 82},
        {"Tom", 91}
    };

    for (const auto& [name, score] : students) {
        cout << name << ": " << score << '\n';
    }

    auto [maximum, minimum, average] = analyze(students);

    cout << "最高分:" << maximum << '\n';
    cout << "最低分:" << minimum << '\n';
    cout << "平均分:" << average << '\n';

    return 0;
}

知识总结

板书式速记

// 1. 基本语法
auto [a, b] = object;

// 2. 修改原对象
auto& [a, b] = object;

// 3. 只读且避免复制
const auto& [a, b] = object;

// 4. 遍历 map
for (const auto& [key, value] : mp) {
    // ...
}

// 5. 接收函数的多个返回值
auto [x, y, z] = function();