在 C++ 中,“结构化绑定(structured binding)” 是 C++17 引入的一项实用语法,可以把一个复合对象里的多个成员一次性拆解出来,分别赋给多个独立的变量。
平时我们读取复合对象里的数据时,反复写 .first、.second 或 std::get<0>() 不太直观。使用结构化绑定之后,就像 “拆包裹” 一样,一行代码就能把里面的各个成员取出来并起好名字,如下图所示。

C++ 结构化绑定的使用
在 C++ 中,结构化绑定的写法非常简洁:我们在 auto 关键字后面跟上一对方括号 [],并在方括号里依次写出各个新变量的名字即可。
语法:
auto [变量名1, 变量名2, ...] = 复合对象;说明:
这里的方括号 [] 不是数组下标,而是用来接收各个成员的变量列表。需要注意的是,方括号前面必须写 auto 关键字,由编译器自动推导每个成员的类型,不能手动写成 int [a, b]。
在实际开发中,结构化绑定支持直接拆解以下 3 类常见对象:
- 标准库中的 std::pair 与 std::tuple。
- 已知长度的原生数组。
- 非静态成员变量全部为
public公开的结构体或类。
示例 1:拆解 pair 与 tuple
#include <iostream>
#include <string>
#include <tuple>
#include <utility>
int main() {
std::pair<std::string, int> stu = {"Jack", 92};
auto [name, score] = stu;
std::cout << name << ":" << score << '\n';
std::tuple<int, int, int> color = {255, 128, 64};
auto [r, g, b] = color;
std::cout << "RGB:" << r << ", " << g << ", " << b << '\n';
return 0;
}运行结果如下。
Jack:92
RGB:255, 128, 64分析:
auto [name, score] = stu; 这句代码表示把 stu 中的 first 和 second 依次拆给变量 name 和 score。后面的 auto [r, g, b] = color; 则是把 color 里的 3 个整数按顺序赋给 r、g 和 b。
对于上面例子中的 stu 来说,下面 2 种写法是等价的。
// 写法 1
std::string name = stu.first;
int score = stu.second;
// 写法 2(推荐)
auto [name, score] = stu;可以看到,写法 2 省去了多行重复的成员访问代码,可读性更好,在实际开发中我们更推荐使用写法 2。
除了 std::pair 和 std::tuple 之外,如果我们要拆解原生数组和自定义结构体,又应该怎么写呢?下面我们再来看一个例子。
示例 2:拆解数组与结构体
#include <iostream>
struct Point {
int x;
int y;
};
int main() {
int arr[2] = {10, 20};
auto [a, b] = arr;
std::cout << "a = " << a << ", b = " << b << '\n';
Point pt = {30, 40};
auto [px, py] = pt;
std::cout << "px = " << px << ", py = " << py << '\n';
return 0;
}运行结果如下。
a = 10, b = 20
px = 30, py = 40分析:
在这个例子中,auto [a, b] = arr; 按下标顺序把数组的两个元素拆给了 a 和 b。auto [px, py] = pt; 则是按照结构体成员定义的先后顺序,把 pt.x 和 pt.y 依次拆给了 px 和 py。
由于 struct 的成员默认就是 public 公开的,因此我们可以直接拆解。如果换作包含 private 私有成员的 class,在类的外部就不能直接拆解了。
另外小伙伴们要注意,方括号里的变量个数必须和成员总个数一致,不能只拆解其中一部分。比如下面的数组有 3 个元素,只写两个变量接收就会在编译时报错。
int arr[3] = {10, 20, 30};
auto [x, y] = arr; // 变量个数不匹配按值绑定与引用绑定
拆出新变量之后,如果我们修改这些变量的值,原来的复合对象会不会跟着改变呢?这取决于我们在 auto 后面有没有加上引用符号(&)。
语法:
auto [变量名1, 变量名2, ...] = 复合对象;
auto& [变量名1, 变量名2, ...] = 复合对象;
const auto& [变量名1, 变量名2, ...] = 复合对象;说明:
这 3 种绑定方式的区别如下:
- auto [a, b]:按值绑定。编译器会先把原对象复制一份副本,我们修改
a或b不会影响原对象。 - auto& [a, b]:引用绑定。拆出的
a和b直接成为原对象成员的引用别名,修改它们就相当于直接修改原对象。 - const auto& [a, b]:只读引用绑定。它既省去了复制整个对象的开销,又不允许通过
a和b修改原对象。
在实际开发中,如果我们只是读取数据,优先使用 const auto& 即可,遇到包含字符串等较大成员的对象时能省去复制开销。需要修改原对象成员时使用 auto&,确实需要独立副本时再使用 auto。
示例 3:对比按值与引用绑定
#include <iostream>
#include <string>
struct Student {
std::string name;
int score;
};
int main() {
Student stu = {"Lucy", 90};
auto [valName, valScore] = stu;
valScore = 100;
std::cout << "按值绑定修改后,原成绩:" << stu.score << '\n';
auto& [refName, refScore] = stu;
refScore = 100;
std::cout << "引用绑定修改后,原成绩:" << stu.score << '\n';
return 0;
}运行结果如下。
按值绑定修改后,原成绩:90
引用绑定修改后,原成绩:100分析:
当我们第 1 次使用 auto [valName, valScore] = stu; 时,采用的是按值绑定。此时把 valScore 改成 100 只是修改了复制出来的副本,原有的 stu.score 依然是 90。
当我们第 2 次使用 auto& [refName, refScore] = stu; 时,refScore 直接成了 stu.score 的引用别名。因此把 refScore 改成 100 之后,原有的 stu.score 也会跟着变成 100。
结构化绑定的常见应用
在实际项目开发中,结构化绑定最常用于 “接收函数返回的多个结果” 以及 “遍历键值对容器” 这两个场景。
1. 接收函数返回的多个值
在 C++ 中,普通函数一次只能返回一个值。比如计算两数相除时,我们希望同时拿到 “商” 和 “余数”,就可以让函数返回一个 std::pair 或自定义结构体,再在调用处通过结构化绑定拆成两个变量接收。
示例 4:接收函数多个返回值
#include <iostream>
#include <utility>
std::pair<int, int> divide(int a, int b) {
return {a / b, a % b};
}
int main() {
auto [quot, rem] = divide(17, 5);
std::cout << "商:" << quot << '\n';
std::cout << "余数:" << rem << '\n';
return 0;
}运行结果如下。
商:3
余数:2分析:
在 divide() 函数中,return {a / b, a % b}; 会根据返回类型自动构造一个包含商 3 和余数 2 的 std::pair<int, int>。回到 main() 函数后,我们通过 auto [quot, rem] 直接就拿到了商和余数,不用再去写 .first 和 .second。
2. 遍历 map 容器
配合范围 for 循环使用时,结构化绑定还能大大简化键值对容器的遍历代码。因为 std::map 里的每一个元素都是一个 pair,我们可以在循环头里直接把 “键” 和 “值” 拆成两个名字清晰的变量。
示例 5:遍历并修改 map 元素
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> scores = {
{"Jack", 90},
{"Lucy", 95},
{"Tony", 88}
};
// 修改每个学生的成绩
for (auto& [name, score] : scores) {
score += 5;
}
// 只读遍历并打印结果
for (const auto& [name, score] : scores) {
std::cout << name << ":" << score << '\n';
}
return 0;
}运行结果如下。
Jack:95
Lucy:100
Tony:93分析:
从代码可以看到,第 1 个循环使用 auto& [name, score] 引用绑定,直接给每个学生的成绩加上了 5 分。第 2 个循环只是读取并打印数据。平时不需要修改元素时,我们优先写成 const auto& [name, score] 即可,既能省去复制字符串的开销,又能防止误改数据。
注意 在遍历 std::map 时,每个元素的键(key)默认带有 const 只读限制。如果直接修改键的值会破坏内部的排序结构,因此即使写成 auto& [name, score],小伙伴们也只能修改值 score,不能修改键 name。
