avatar

目录
293 - cpp 结构化绑定语法讲解

C++ 结构化绑定语法讲解

🧑‍💻 User

In this lecture we’re going to learn about structured bindings, and structured bindings are a technique.We have to access member variables of a class using some simple syntax.And suppose we have a struct which is really a class with public member variables inside.And this is going to be modeling a point in C plus plus.So we’re going to have the coordinates of a point which are double X and double y here.We could want to access the member variables of this struct very easily using the syntax we see here.So what we do is we use the auto keyword and then we put square brackets and inside we put the placeholdersfor the member variables of our struct.And if we do something like this, we basically going to have a copy of the x member variable storedin a and then we’re going to have a copy of the Y member variable stored in B here.So the order you declare things in is important.So we’re going to go from the first to the last and then we’re going to go from the first to the lastin this order here.So what this is going to do, it’s going to assign the member variables of our point object to A andB here.This is what this syntax here is going to do.And one thing you should know is that this is only going to work if your member variables are public.If they are not public, you’re going to get compiler errors because this is really like accessing usingthe syntax here 0.1 x and 0.1 Y, and the compiler is going to put together the machinery to make thiswork behind the curtains of how it works.But this is going to work.And one thing you should really, really keep in mind is that what you have in A and B are going tobe copies.So if after we happen to change our object and for example, in X, we put 10.1 and in Y, we put 66.2like we do here, and then we print A and B, A and B are not going to pick up.This changes because they are just copies.So this is the syntax for structured bindings.There is a lot more to this, especially with other data structures in C plus plus.But for now we’re going to use this in the context of a struct.Another thing you should know is that it is possible to capture a structured bindings inside a lambdafunction and this is something that was added in C plus plus 20.So for example, we can capture the a variable here in a lambda function and we can use it inside thelambda function like this.Another thing you should know is that at the moment it is not possible to do structured bindings thatuse references.If you want to do that, you doomed.You shouldn’t use structured bindings.But I just want you to know about this syntax.And in this lecture we are learning about it in the context of struct because structured bindings cando really, really much more, especially in more advanced data structures, as we will see later inthe course.But for now, I just want you to be aware of the syntax here and it shouldn’t be foreign to you if yousee it starting from today.Now that you know this, we’re going to head over to Visual Studio code and play with this a littlemore.Okay.Here we are in our working folder.The current project is Structured Bindings.We’re going to grab our template files and we’re going to put them in place.And let’s open this in Visual Studio code by dragging and dropping here, this is going to bring usour folder here and we can close the left sidebar.We don’t need it here and we’re going to put together a struct.It is going to be struct because we want to have public access to our member variables.We’re going to name this point and we’re going to put data inside.We’re not going to put anything complicated in here.We’re just going to put in double X and double Y, and this is going to model our point in C plus plus.What we can do, for example, is set up a point.Let’s do that in main.We’re going to call this point one and then we can say point one X and assign a value to this, let’ssay five and say point one Y, and we’re going to assign a six to this.And at the moment we do this, we can print this point.For example, let’s put in a function which is going to print a point.We’re going to say void print point because we don’t want to do stdcout every time.So we’re going to say print point here and we’re going to take a point by reference.And inside the body we’re going to say Stdcout point.We can save that and we’re going to say X and Y for this point here.So we’re going to say p x and we’re going to say Y.It’s going to be p y.This is how we access member variables of a struct directly and we’re going to put the closing squarebracket and we’re going to end this with Stdendl.I think this could work.So now we could come here and.The main function and say print point and pass 0.1 here.This should work.So let’s build and run this.We’re going to use GCC.You are going to see that the world is good so we can bring up a terminal and really play with this.Let’s clear and run, Rooster.You’re going to see that we have our point, but we can really access this point using structured bindingnotation.And the way we do that, we say auto and we put square bracket and then we say the variables that aregoing to capture the member variables of our struct.So we’re going to assign the point to one to this thing.And what this is going to do is assign the X member variable to A and assign the Y member variable toB, And now if we print A and B, we’re going to have the member variables printed out.Okay, So let’s do that.We’re going to say Stdcout A and we’re going to see the value printed out here and we’re going to printB and we’re going to see the value printed out here.Okay, let’s do that.We’re going to world.The world is good.We can clear and run Rooster.This is going to give us A and B.Another thing you should know is that A and B are copies.They’re not references or anything that has anything to do with the member variables we have in thestructure here.So we’re going to prove this.We’re going to change X and Y and our object, we’re going to print the point and then we’re going toprint A and B, and we see that nothing was affected in A and B here.So let’s do that.We’re going to say 0.1 X and we’re going to change this to anything really.Let’s put in 44.1 and then we’re going to say 0.1 Y and we’re going to put in 55.2.Why not?And then we’re going to print zero point.So we’re going to say print point and we’re going to print 0.1 again.And after this, we’re also going to say what we have in A and B, okay, So let’s clean this up a littlebit, because I think we have a lot of spaces in here, but we can space these things out because Iwant you to see them.What we are doing here, we are setting up a point, we are putting in values and then printing thepoint.We are using structured binding syntax here to assign the member variables of this point object to Aand B So A is going to have the value we have in X and B is going to have the value we have in Y.If we print this, we’re going to see that.Then we’re going to modify our point and then we’re going to print the modified point.But we’re going to see that because A and B are just copies.The change we do here are not going to be reflected in the second print we do for A and B here.This is the goal we have.So let’s build again.The build is good.We can clear and run Rooster.So you see that A and B are five and six first here, and then we modify our point and make it to havean X value of 44.1 and a Y value of 55.2.But you see that A and B don’t grab these changes because A and B are just copies.Please note that at the moment it is not possible to do structured bindings that capture references.So for example, if you’re trying to do something like this, that’s something somebody might do.If you do something like this, you’re going to get compiler errors.This is not supported.Let’s try and build and show you the problem.The compiler error is going to be really weird and we’re going to say expected identifier, AmpersandToken.This is really not supported.This is what the compiler error is trying to say here.So let’s take this out.And the last thing I want to show you is that it is possible to capture structured bindings in a lambdafunction.And if this is something that is important or useful for whatever project you are working on, pleaseuse this.So for example, we can set up a lambda function.We’re going to say auto func and we’re going to say the capture list is going to have a we’re goingto capture the structured bindings variable here and then we’re going to put in our parameter list andthen we’re going to put in our body and then we’re going to print a here, we’re going to do StdcoutA and say that it was captured and we’re going to print this out and then we’re going to call this lambdafunction here so we can say func and this is going to call our lambda function.Let’s build and see that this is actually going to work.So this is working properly.But notice that if you don’t capture a, you’re going to get a weird compiler error.You see we have a squiggly line already here and if you want, you’re going to see that you’re goingto have a compiler error along the lines of the a variable here was not captured.Okay.So we’re going to have the lambda has no capture default auto func and it’s going to say that A wasnot declared in the lambda.A function here.So this is the error you get.You need to capture A to be able to use it in the lambda function here.And I have to say that this is something that is possible in C plus plus 20.In earlier versions of C plus plus, this is going to give you a compiler error.So you should know this.Let’s build and show you that this is going to work.We should see a captured printed here on the console.Let’s clear and run Rooster.And we have a captured five here.If you want, you can capture everything by value.You can use the equal sign to do that.You already know this and this is also going to work and this is also going to capture B, let’s doStdcout We’re going to say be captured and we’re going to print B here.And this is going to work because we have captured everything in the context here in our Lambda function.If the capturing stuff is confusing, please go back and watch the chapter on lambda functions.We talked about this Thanks extensively, but for now we’re going to build and run this.We’re going to use GCC for that.You see, the world is good.We can clear and run Rooster and we’re going to see that B is also captured and the value is in here.This is really all we set out to do in this lecture, teaching you about structured bindings.The syntax is what you see here.And if you do this, you’re going to have a slightly nicer way to use the member variables of your struct.One thing I don’t think I talked about is that structured bindings like we do here are only going towork if you have public member variables.If for example, we change the struct here to be a class, this is going to make the member variablesprivate by default and we will have all kinds of crazy problems.But you see that even on our structured binding we have a squiggly line.This means that we can’t really use structured bindings on a class which has its member variables private.So if we change this to be public, let’s do that.We’re going to see that the squiggly lines are going to go away because the structured bindings onlywork if your member variables are public here.Please remember this and if you remember this, you’re going to use structured bindings however youwant.Another thing I should mention about structured bindings is that it is critical to use the auto keywordhere.So if, for example, you change this to double, let’s do that because some of you are going to askthis question.We’re going to see that we have a squiggly line and it is saying invalid specifier for structured bindingdeclaration.So structured bindings are only going to work if you specify the auto keyword here.This is what the Cplusplus compiler is going to expect.If you violate this, you’re going to get weird compiler errors.This is really all we set out to do in this lecture.I hope you found it interesting.We are going to stop here in this one.In the next one, we’re going to try and recap what we saw in this chapter.Go ahead and finish up here and meet me there.


🤖 Assistant

好的,没问题!作为你的 C++ 启蒙老师,我会尽我所能用最清晰、最易懂的方式来教你。今天我们来学习一个 C++17 引入的很酷的特性——结构化绑定!


1. 标题和摘要

标题: C++ 结构化绑定 (Structured Bindings) 入门:轻松访问成员变量

摘要:
本节课我们将学习 C++ 中的结构化绑定 (Structured Bindings)。这是一种自 C++17 起引入的便捷语法,允许我们用一种非常简洁的方式直接将 struct (结构体) 或 class (类)(当其成员是公开的时)的成员变量分解到独立的具名变量中。我们将重点了解它的基本用法、它是如何创建成员副本的,以及它在 public (公有) 成员访问权限下的工作方式。


2. 详细解释

让我们一步一步来揭开结构化绑定的神秘面纱。

2.1 什么是结构化绑定 (Structured Bindings)?

想象一下,你有一个表示二维空间点的结构体,它有两个成员:x 坐标和 y 坐标。

cpp
1
2
3
4
struct Point {
double x;
double y;
};

在没有结构化绑定之前,如果你想分别获取 x 和 y 的值,你可能会这样做:

cpp
1
2
3
Point p = {10.0, 20.0};
double coord_x = p.x;
double coord_y = p.y;

这没什么问题,但如果成员变量很多,或者你想让代码更简洁,结构化绑定就派上用场了。它允许你用一行代码就“解包” Point 对象的成员到单独的变量中。

2.2 结构化绑定的基本语法

结构化绑定的核心语法如下:

cpp
1
auto [变量名1, 变量名2, ...] = 对象实例;
  • auto:这是必须使用的关键字 (keyword)。编译器会自动推断 变量名1, 变量名2 等的类型。你不能在这里显式指定类型(比如用 double 替换 auto)。
  • [] (方括号):里面是你为解包出来的成员变量起的新名字,用逗号隔开。
  • =:赋值操作符。
  • 对象实例:通常是一个 structclass(具有公开成员)、std::pairstd::tuple 或数组的实例。

对于我们上面的 Point 例子,使用结构化绑定会是这样:

cpp
1
2
3
Point p1 = {5.0, 6.0};
auto [a, b] = p1; // 这就是结构化绑定!
// 此时,a 的值是 p1.x (5.0),b 的值是 p1.y (6.0)

2.3 它是如何工作的?——顺序的重要性

结构化绑定会按照 structclass 中成员变量的声明顺序将它们的值赋给方括号中对应位置的变量。

  • Point 中第一个声明的成员是 x,所以 p1.x 的值赋给了方括号中的第一个变量 a
  • Point 中第二个声明的成员是 y,所以 p1.y 的值赋给了方括号中的第二个变量 b

所以,auto [first, second] = p1;auto [myX, myY] = p1; 都是可以的,变量名由你决定,但它们总是按顺序接收值。

2.4 关键特性:默认创建的是副本 (Copies)

这是非常重要的一点!当你使用 auto [a, b] = p1; 这样的形式时,变量 ab 得到的是 p1.xp1.y副本 (copy)

这意味着:

  • ab 是独立于 p1 成员的新变量。
  • 如果在结构化绑定之后修改了 p1.xp1.y 的值,ab 的值不会改变。
  • 同样,修改 ab 的值也不会影响 p1.xp1.y

我们会在下面的代码示例中清楚地看到这一点。

2.5 访问权限:仅适用于公有 (Public) 成员

结构化绑定在尝试访问对象的成员时,遵循 C++ 的访问权限规则。它本质上是编译器为你自动生成类似 object.member1, object.member2 这样的访问代码。

  • 对于 struct (结构体),成员默认是 public (公有) 的,所以结构化绑定可以直接使用。
  • 对于 class (类),成员默认是 private (私有) 的。如果你尝试对一个具有 private 成员的 class 对象使用结构化绑定,会导致编译错误 (compiler error),因为外部代码无权访问私有成员。
    • 如果 class 的成员被显式声明为 public,那么结构化绑定就可以用于这个 class 对象。

2.6 auto 关键字是强制的

如前所述,你必须使用 auto 关键字。如果你尝试用具体的类型(如 double)来代替 auto,比如:

cpp
1
2
// 错误示范!
double [val_x, val_y] = p1; // 这会导致编译错误

编译器会报错,因为它期望 auto 来配合结构化绑定的声明。

2.7 关于引用 (References) 的说明 (根据课程内容)

课程内容特别提到,在当前(或者说,在它描述的C++版本或特定上下文中)不直接支持通过结构化绑定来创建原始成员的引用 (reference),例如尝试这样做:

cpp
1
2
// 根据课程内容,以下方式不被支持,会导致编译错误
// auto& [ref_x, ref_y] = p1;

课程指出这样做是“注定要失败的”,并建议如果需要引用,则不应使用结构化绑定。
(助教小提示: 尽管现代C++ (C++17及以后) 实际上是支持 auto&auto&& 的结构化绑定的,从而可以绑定到引用。但为了遵循你提供的课程材料,我们在这里强调课程中的观点。了解课程材料的特定教学点很重要。)

2.8 结构化绑定与 Lambda 函数 (Lambda function) (C++20)

从 C++20 开始,结构化绑定声明的变量可以被捕获到 Lambda 表达式 (Lambda function) 中。这非常方便。

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
Point p_lambda = {7.0, 8.0};
auto [x_val, y_val] = p_lambda;

auto my_lambda = [x_val] { // 按值捕获 x_val
std::cout << "Lambda captured x_val: " << x_val << std::endl;
};
my_lambda(); // 调用 lambda

// 也可以按值捕获所有在作用域内的变量,包括结构化绑定产生的变量
auto another_lambda = [=] {
std::cout << "Lambda captured x_val: " << x_val << " and y_val: " << y_val << std::endl;
};
another_lambda();

如果 lambda 试图使用一个未被捕获的结构化绑定变量,编译器会报错,提示该变量未在 lambda 的捕获列表 (capture list) 中。

2.9 结构化绑定的适用范围

虽然本课主要关注 struct,但结构化绑定也适用于其他类型,比如:

  • std::pair
  • std::tuple
  • 原生数组 (C-style arrays)

这使得从这些数据结构中提取元素变得异常简单。


3. 代码示例

让我们通过一个完整的例子来演示以上知识点。

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#include <iostream>
#include <string>
#include <vector> // 只是为了包含一些常用头文件,本例中未直接使用 vector

// 定义一个表示点的结构体
struct Point {
double x;
double y;
// 默认成员是 public
};

// 如果我们用 class 并且不指定 public,成员默认是 private
class PointClassPrivate {
double x_coord; // 默认 private
double y_coord; // 默认 private
public:
PointClassPrivate(double x, double y) : x_coord(x), y_coord(y) {}
// 如果想让结构化绑定工作,需要提供 public 的 getter 方法
// 或者将成员设为 public (如下面的 PointClassPublic)
};

class PointClassPublic {
public:
double x_coord; // 显式声明为 public
double y_coord; // 显式声明为 public
};


void printPoint(const Point& p) {
std::cout << "Point: [x=" << p.x << ", y=" << p.y << "]" << std::endl;
}

int main() {
std::cout << "--- 结构化绑定基础 ---" << std::endl;
Point p1 = {5.0, 6.0};
std::cout << "Original p1: ";
printPoint(p1);

// 使用结构化绑定
auto [a, b] = p1; // a 获取 p1.x 的副本, b 获取 p1.y 的副本

std::cout << "Structured binding: a = " << a << ", b = " << b << std::endl;

std::cout << "\n--- 证明 a, b 是副本 ---" << std::endl;
// 修改原始对象 p1 的成员
p1.x = 44.1;
p1.y = 55.2;
std::cout << "Modified p1: ";
printPoint(p1);

// 再次打印 a 和 b,它们的值没有改变
std::cout << "a and b after p1 modification: a = " << a << ", b = " << b << std::endl;
std::cout << "(See? a and b were copies and were not affected by changes to p1.x and p1.y)" << std::endl;

// 修改 a 和 b,p1 的成员也不会改变
a = 100.0;
b = 200.0;
std::cout << "\nModified a = " << a << ", b = " << b << std::endl;
std::cout << "p1 after a and b modification: ";
printPoint(p1);
std::cout << "(p1 was not affected by changes to a and b)" << std::endl;

std::cout << "\n--- auto 关键字是必须的 ---" << std::endl;
// 下面的代码如果取消注释,会导致编译错误
// double [x_coord, y_coord] = p1;
// std::cout << "This won't compile." << std::endl;
std::cout << "Attempting 'double [x_c, y_c] = p1;' would cause a compiler error." << std::endl;
std::cout << "Error message might be: 'invalid specifier for structured binding declaration'" << std::endl;


std::cout << "\n--- 成员访问权限 ---" << std::endl;
std::cout << "Using struct (default public members): Works!" << std::endl;
Point p_struct = {1.1, 2.2};
auto [sx, sy] = p_struct;
std::cout << "Struct binding: sx = " << sx << ", sy = " << sy << std::endl;

std::cout << "\nUsing class with explicit public members: Works!" << std::endl;
PointClassPublic p_class_pub = {3.3, 4.4};
auto [cpx, cpy] = p_class_pub; // 访问 p_class_pub.x_coord 和 p_class_pub.y_coord
std::cout << "Class public binding: cpx = " << cpx << ", cpy = " << cpy << std::endl;

std::cout << "\nUsing class with default private members: Fails!" << std::endl;
PointClassPrivate p_class_priv(5.5, 6.6);
// 下面的代码如果取消注释,会导致编译错误
// auto [cvx, cvy] = p_class_priv; // 错误:x_coord 和 y_coord 是私有的
// std::cout << "This won't compile because members are private." << std::endl;
std::cout << "Attempting 'auto [cvx, cvy] = p_class_priv;' would cause a compiler error." << std::endl;
std::cout << "Error might be related to inaccessible members." << std::endl;


std::cout << "\n--- 结构化绑定与 Lambda (C++20) ---" << std::endl;
Point p_lambda_demo = {7.7, 8.8};
auto [lx, ly] = p_lambda_demo;

// 捕获单个结构化绑定变量
auto lambda1 = [lx]() { // 按值捕获 lx
std::cout << "Lambda1 (captures lx): lx = " << lx << std::endl;
// ly = 10; // 错误: ly 未被捕获 (如果取消注释)
// 同时,lx 是按值捕获的副本,修改它不影响外部的 lx
};
lambda1();

// 使用默认按值捕获 [=] 来捕获所有用到的外部变量
auto lambda2 = [=]() {
std::cout << "Lambda2 (captures with [=]): lx = " << lx << ", ly = " << ly << std::endl;
};
lambda2();

// 尝试使用未捕获的变量会导致编译错误
auto lambda_error_example = []() {
// std::cout << "Trying to use lx without capture: " << lx << std::endl; // 这会编译失败
};
std::cout << "A lambda trying to use 'lx' or 'ly' without capturing them would fail to compile." << std::endl;


std::cout << "\n--- 关于引用绑定 (根据课程内容) ---" << std::endl;
// 根据课程说明,以下方式是不支持的
// Point p_ref_test = {9.1, 9.2};
// auto& [rx, ry] = p_ref_test; // 课程指出这会产生编译错误 "expected identifier before ‘&’ token"
std::cout << "As per the lecture, 'auto& [rx, ry] = p_obj;' is stated to cause a compiler error." << std::endl;
std::cout << "The lecture suggests this syntax for reference binding is not supported." << std::endl;

return 0;
}

编译和运行说明:
如果你用的是支持 C++17 (或更高版本,对于 lambda 捕获结构化绑定则需要 C++20) 的编译器 (compiler),例如 GCC 7+ 或 Clang 5+:

bash
1
2
g++ -std=c++17 your_file_name.cpp -o structured_bindings_demo
./structured_bindings_demo

如果需要 lambda 捕获结构化绑定的功能,请使用 -std=c++20
bash
1
2
g++ -std=c++20 your_file_name.cpp -o structured_bindings_demo
./structured_bindings_demo


4. 问答卡片 (QA Flash Cards)

这里有一些卡片,帮助你巩固记忆:

  1. 问: 什么是结构化绑定 (Structured Bindings)?
    答: C++17 引入的一种语法,允许将 structclass (公有成员)、pairtuple 或数组的成员/元素直接解包到独立的变量中。

  2. 问: 结构化绑定的基本语法是什么?
    答: auto [var1, var2, ...] = object;

  3. 问: 在结构化绑定 auto [a, b] = obj; 中,abobj 成员的副本还是引用?
    答: 默认情况下,它们是副本 (copies)。

  4. 问: 结构化绑定要求对象的成员具有什么访问权限?
    答: public (公有) 访问权限。

  5. 问: 在声明结构化绑定时,必须使用哪个关键字来代替具体的类型?
    答: auto 关键字。

  6. 问: 结构化绑定解包成员是基于成员名称还是声明顺序?
    答: 基于成员在 structclass 中的声明顺序。

  7. 问: 结构化绑定声明的变量可以被 Lambda 函数捕获吗?
    答: 可以,自 C++20 起,它们可以像普通变量一样被捕获。

  8. 问: 根据本课程的讲解,如果尝试 auto& [x, y] = obj; 会怎样?
    答: 根据本课程的讲解,这会导致编译错误,因为课程指出这种形式的引用绑定不被支持。


5. 常见误解或用户可能犯的错误

  1. 误以为是引用绑定: 最常见的误解是认为 auto [a, b] = obj; 中的 abobj 成员的引用。然后惊讶地发现修改 obj.x 并没有改变 a 的值。记住,默认是副本
  2. 忘记 auto 关键字: 新手可能会尝试写 MyStructType [var1, var2] = myStructInstance; 或者 double [x, y] = myPoint;。这是错误的,必须使用 auto
  3. 对私有成员使用: 尝试对一个 class 的私有成员使用结构化绑定,例如 MyClass instance; auto [m1, m2] = instance; (其中 m1, m2 对应 MyClass 的私有成员),这将导致编译失败。
  4. 变量名和成员名不匹配的困惑: auto [apple, banana] = myPoint; 这里的 apple 仍然会得到 myPoint.x 的值(如果 x 是第一个成员),banana 得到 myPoint.y 的值(如果 y 是第二个成员)。变量名是你自己起的,和原始成员名无关,只和顺序有关。
  5. 对课程中关于 auto& 的说明感到困惑: 用户可能会在别处看到 auto& 用于结构化绑定并成功编译,然后对本课程中指出的“不支持”感到困惑。关键在于理解本课程的教学上下文——它可能基于一个较早的理解或特定的编译器行为来简化教学。(助教再次提示:现代C++是支持的,但我们需根据你提供的课程材料来教学)
  6. Lambda 捕获问题 (C++20前): 在 C++20 之前,直接捕获结构化绑定中声明的变量到 lambda 中是不支持的或有问题的。如果用户的编译器不是 C++20 模式,尝试捕获可能会失败。
  7. 不理解顺序的重要性: 如果一个结构体是 struct Data { int id; std::string name; };,那么 auto [s, i] = myData; 将导致 s 获得 id 的值,i 获得 name 的值,这可能不是用户期望的,因为类型不匹配(std::string 赋给 int 或反之),可能导致编译错误或隐式转换问题。

6. 编码练习

现在,轮到你来试试身手了!下面有一个 Student 结构体,请你补全代码,使用结构化绑定来获取 nameage 成员,并打印它们。

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <string>

struct Student {
std::string name;
int age;
// 注意:这里可以添加一个构造函数来方便初始化
Student(std::string n, int a) : name(n), age(a) {}
};

int main() {
Student s1("小明", 12);

// TODO: 在这里使用结构化绑定来从 s1 中获取 name 和 age
// 将 name 存储在名为 studentName 的变量中
// 将 age 存储在名为 studentAge 的变量中
// ______ [____________, ____________] = ___; // 填空

// TODO: 打印 studentName 和 studentAge
// std::cout << "学生姓名: " << ___________ << std::endl;
// std::cout << "学生年龄: " << ___________ << "岁" << std::endl;

return 0;
}

请你思考一下,然后在空白处填上正确的代码。完成后告诉我你的答案,我会帮你检查!加油!


🧑‍💻 User

开始之前我问个问题, 是不是说只要有private变量或者函数的class就不能用structure binding了?


🤖 Assistant

问得好!这是一个非常关键的点,能帮助你更深入地理解结构化绑定的工作原理。

简单来说,不完全是“只要有 private 变量或函数的 class 就不能用”。更准确的说法是:

结构化绑定 (Structured Binding) 要求它尝试访问并解包的那些非静态数据成员 (non-static data members) 必须是 public (公有的)。

让我详细解释一下:

  1. 针对数据成员 (Data Members): 结构化绑定是用来“解开”对象的数据成员的。它会按照这些数据成员在类或结构体中声明的顺序,依次将它们赋值给你在方括号 [] 中提供的变量。
  2. 访问权限检查 (Access Check): 当结构化绑定尝试访问这些数据成员时,C++ 的访问权限规则会生效。如果它试图访问一个 private (私有) 或 protected (受保护) 的成员(并且当前上下文无权访问),编译器就会报错。
  3. 所有参与绑定的成员都必须可访问: 结构化绑定会尝试解包从第一个非静态数据成员开始的所有成员(或者直到你提供的变量数量用尽为止)。如果在这个过程中,遇到的任何一个需要被解包的成员是 privateprotected,那么整个结构化绑定就会失败。
  4. 私有函数 (Private Functions) 的影响: 类中是否有私有成员函数通常不直接影响结构化绑定。结构化绑定关心的是数据成员。只要那些它需要解包的数据成员是 public 的,即使类里有私有函数,结构化绑定也能工作。
  5. 混合访问权限 (Mixed Access Specifiers):
    • 如果一个类有部分 public 数据成员和部分 private 数据成员:
      cpp
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      class MixedAccess {
      public:
      int public_data;
      private:
      int private_data; // 这个是私有的
      public:
      std::string another_public_data;

      MixedAccess(int p, int priv, std::string s) : public_data(p), private_data(priv), another_public_data(s) {}
      };

      MixedAccess obj(1, 2, "hello");
      // auto [a, b, c] = obj; // 编译错误!因为 private_data 是私有的,无法在外部通过结构化绑定访问。
      // 结构化绑定会按顺序尝试访问 public_data, private_data, another_public_data
      在这个例子中,即使 public_dataanother_public_data 是公有的,但因为 private_data 夹在中间(按声明顺序)并且是私有的,所以当你试图解包所有三个成员时,结构化绑定会失败。
    • 如果所有你想绑定的成员都在 private 成员之前声明并且是 public 的,那么部分绑定是可能的,但这依赖于结构化绑定如何处理成员数量不匹配的情况(通常它会尝试绑定所有非静态数据成员)。但更安全的理解是,所有将被解包的成员都需要是公有的。
      C++标准规定,结构化绑定会考虑类中所有的非静态数据成员。如果其中任何一个是不可访问的,那么绑定就会失败。

总结一下:

  • 不是 “只要有 private 成员就完全不能用”。
  • 而是 “结构化绑定试图访问的所有非静态数据成员都必须是 public 的”。
  • 如果一个类所有数据成员都是 private 的,那么你肯定不能对其实例使用结构化绑定。
  • 如果一个类的数据成员有 public 也有 private,那么结构化绑定会因为无法访问 private 成员而失败。
  • 类中的 private 成员函数本身不阻止对 public 数据成员的结构化绑定。

所以,你提出的问题触及了核心,关键在于哪些成员private 的(数据成员还是函数成员),以及结构化绑定具体要访问哪些成员。对于 class,如果你想用结构化绑定,通常需要确保所有非静态数据成员都是 public 的(这使得它在行为上更像一个 struct)。

现在你可以继续尝试填写练习代码了!如果还有疑问,随时问我。


🧑‍💻 User

那如果是这个class有method呢?


🤖 Assistant

好问题!这个问题能帮助我们进一步明确结构化绑定的作用对象。

对于一个有成员函数 (methods) 的 class,结构化绑定的行为如下:

  1. 结构化绑定主要关注的是类的非静态数据成员 (non-static data members)。 它尝试将这些数据成员的值“解包”到你提供的独立变量中。
  2. 成员函数 (methods) 本身不参与结构化绑定的解包过程。 你不能通过结构化绑定将一个成员函数赋值给一个变量。成员函数是类的行为或操作,而不是可以直接提取并赋值给独立变量的数据片段。
  3. 只要那些将被结构化绑定访问和解包的非静态数据成员是 public (公有的),即使类中包含成员函数(无论是 publicprivate 还是 protected),结构化绑定仍然可以正常工作。

简单来说:类中有成员函数不会阻止结构化绑定,前提是它要绑定的数据成员是公有的。

让我们看一个例子:

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
#include <string>
#include <cmath> // 为了 std::sqrt

class Circle {
public:
double radius; // 公有数据成员
std::string color; // 公有数据成员

// 构造函数
Circle(double r, std::string c) : radius(r), color(c) {}

// 公有成员函数
double getArea() {
return 3.14159 * radius * radius;
}

// 公有成员函数
void describe() {
std::cout << "This is a " << color << " circle with radius " << radius
<< ". Area: " << getArea() << std::endl;
}

private:
// 私有成员函数 (不影响结构化绑定对公有数据成员的访问)
void internalCalculation() {
std::cout << "Performing internal calculation..." << std::endl;
}
};

int main() {
Circle c1(5.0, "Red");
c1.describe(); // 调用成员函数

// 使用结构化绑定来获取公有的数据成员 radius 和 color
// 成员函数 getArea(), describe(), internalCalculation() 不会被绑定
auto [rad, col] = c1;

std::cout << "\nAfter structured binding:" << std::endl;
std::cout << "Radius extracted: " << rad << std::endl;
std::cout << "Color extracted: " << col << std::endl;

// rad 和 col 是 c1.radius 和 c1.color 的副本
rad = 10.0; // 修改 rad 不会影响 c1.radius
std::cout << "c1.radius after modifying 'rad': " << c1.radius << std::endl; // 仍然是 5.0

// 你仍然可以调用对象的成员函数
// double area = c1.getArea(); // 这是可以的

// 你不能这样做:
// auto [r, clr, area_func] = c1; // 错误!结构化绑定不解包成员函数

return 0;
}

在这个例子中:

  • Circle 类有公有数据成员 radiuscolor
  • 它也有公有成员函数 getArea()describe() 和一个私有成员函数 internalCalculation()
  • 结构化绑定 auto [rad, col] = c1; 成功地将 c1.radius 的值赋给了 rad,将 c1.color 的值赋给了 col
  • 成员函数的存在(无论是公有还是私有)并没有影响结构化绑定对公有数据成员的访问。
  • 结构化绑定并没有,也不能,把 getArea()describe() 这样的成员函数“解包”出来。

总结关键点:

  • 结构化绑定的目标是非静态数据成员
  • 成员函数(方法)不参与解包。
  • 只要目标数据成员是 public,类中有无成员函数或成员函数的访问权限如何,都不影响结构化绑定对这些公有数据成员的解包。

所以,你可以放心地在有成员函数的类上使用结构化绑定,只要你关注的是那些可公开访问的数据成员。

现在,你可以尝试完成之前的编码练习了!如果你还有其他问题,尽管问。


🧑‍💻 User

include

include

struct Student {
std::string name;
int age;
// 注意:这里可以添加一个构造函数来方便初始化
Student(std::string n, int a) : name(n), age(a) {}
};

int main() {
Student s1(“小明”, 12);

// TODO: 在这里使用结构化绑定来从 s1 中获取 name 和 age
// 将 name 存储在名为 studentName 的变量中
// 将 age 存储在名为 studentAge 的变量中
// ______ [____________, ____________] = ___; // 填空
auto [studentName, studentAge] = s1;

// TODO: 打印 studentName 和 studentAge
// std::cout << "学生姓名: " << ___________ << std::endl;
// std::cout << "学生年龄: " << ___________ << "岁" << std::endl;
std::cout << "学生姓名: " << studentName << std::endl;
std::cout << "学生年龄: " << studentAge << "岁" << std::endl;

return 0;

}


🤖 Assistant

好的,我们来看一下你的解答!


7. 评价你的解答、给出正确答案和解释

你的解答:

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <string>

struct Student {
std::string name;
int age;
// 注意:这里可以添加一个构造函数来方便初始化
Student(std::string n, int a) : name(n), age(a) {}
};

int main() {
Student s1("小明", 12);

// TODO: 在这里使用结构化绑定来从 s1 中获取 name 和 age
// 将 name 存储在名为 studentName 的变量中
// 将 age 存储在名为 studentAge 的变量中
auto [studentName, studentAge] = s1; // 你填写的代码

// TODO: 打印 studentName 和 studentAge
std::cout << "学生姓名: " << studentName << std::endl; // 你填写的代码
std::cout << "学生年龄: " << studentAge << "岁" << std::endl; // 你填写的代码

return 0;
}

评价:

太棒了!你的解答是完全正确的! 💯

  • 你正确地使用了 auto 关键字。
  • 你使用了方括号 [] 来声明新的变量名 studentNamestudentAge
  • 你将 s1 对象赋值给了结构化绑定声明。
  • studentName 将会按顺序接收 s1.name 的副本,studentAge 将会按顺序接收 s1.age 的副本。
  • 后续的打印语句也正确地使用了这两个新变量。

没有任何问题,非常出色! 这表明你已经理解了结构化绑定的基本用法。

标准答案(与你的答案一致):

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <string>

struct Student {
std::string name;
int age;
// 注意:这里可以添加一个构造函数来方便初始化
Student(std::string n, int a) : name(n), age(a) {}
};

int main() {
Student s1("小明", 12);

// 使用结构化绑定来从 s1 中获取 name 和 age
// 将 name 存储在名为 studentName 的变量中
// 将 age 存储在名为 studentAge 的变量中
auto [studentName, studentAge] = s1;

// 打印 studentName 和 studentAge
std::cout << "学生姓名: " << studentName << std::endl;
std::cout << "学生年龄: " << studentAge << "岁" << std::endl;

return 0;
}

解释:

正如我们在课程中讨论的:

  1. Student 是一个 struct,它的成员 nameage 默认是 public (公有的)。
  2. auto [studentName, studentAge] = s1; 这行代码就是结构化绑定的核心。
    • auto:编译器会自动推断 studentName 的类型为 std::stringstudentAge 的类型为 int
    • [studentName, studentAge]:声明了两个新的变量。studentName 会对应 Student 结构体中声明的第一个成员 (name),studentAge 会对应第二个成员 (age)。
    • = s1:将 s1 对象的值“解包”赋给这些新变量。
  3. 重要的是要记住,studentName 得到的是 s1.name 的一个副本 (copy)studentAge 得到的是 s1.age 的一个副本 (copy)。如果你后续修改 s1.names1.agestudentNamestudentAge 的值不会改变,反之亦然。

你的代码运行后会输出:

Code
1
2
学生姓名: 小明
学生年龄: 12岁

这完全符合预期。


8. 拓展问题与建议

你已经很好地掌握了基础,下面是一些可以让你进一步思考和探索的建议和问题:

  1. 绑定到引用:
    虽然我们之前的课程内容指出 auto& [rx, ry] = obj; 可能会有问题,但实际上从 C++17 开始,结构化绑定是支持绑定到引用的。你可以尝试一下:

    cpp
    1
    2
    3
    4
    5
    Student s2("小红", 10);
    auto& [nameRef, ageRef] = s2;
    nameRef = "小华"; // 修改 nameRef
    ageRef = 11; // 修改 ageRef
    std::cout << "s2.name: " << s2.name << ", s2.age: " << s2.age << std::endl;

    看看 s2 的成员是否真的被修改了。这会帮助你理解副本绑定和引用绑定的区别。请注意,这与之前课程中提到的“不支持”的说法有所不同,这是对现代 C++ 更准确的描述。

  2. 绑定到 const 引用:
    如果你不希望通过结构化绑定修改原始对象,但又想避免不必要的拷贝(特别是对于大的对象),可以绑定到 const 引用:

    cpp
    1
    2
    3
    4
    Student s3("小刚", 13);
    const auto& [constNameRef, constAgeRef] = s3;
    // constNameRef = "新名字"; // 这行代码如果取消注释,会导致编译错误
    std::cout << "Const Ref Name: " << constNameRef << ", Const Ref Age: " << constAgeRef << std::endl;

    思考一下这样做的好处。

  3. 结构化绑定与 std::pairstd::tuple
    结构化绑定不仅仅用于自定义的 structclass。它对于标准库中的 std::pairstd::tuple 非常有用。
    尝试创建一个 std::pair<std::string, int> 和一个 std::tuple<std::string, int, double>,然后使用结构化绑定来获取它们的值。
    例如:

    cpp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include <utility> // for std::pair
    #include <tuple> // for std::tuple

    std::pair<std::string, int> myPair("test_pair", 100);
    auto [str_val, int_val] = myPair;
    std::cout << "Pair: " << str_val << ", " << int_val << std::endl;

    std::tuple<std::string, int, double> myTuple("test_tuple", 200, 3.14);
    auto [s_tuple, i_tuple, d_tuple] = myTuple;
    std::cout << "Tuple: " << s_tuple << ", " << i_tuple << ", " << d_tuple << std::endl;
  4. 结构化绑定与数组:
    你也可以对原生数组使用结构化绑定:

    cpp
    1
    2
    3
    4
    int arr[] = {10, 20, 30};
    auto [x, y, z] = arr;
    std::cout << "Array elements: " << x << ", " << y << ", " << z << std::endl;
    // x = arr[0], y = arr[1], z = arr[2]

    思考一下如果数组元素的数量和方括号中变量的数量不匹配会发生什么?(提示:通常会导致编译错误,数量必须匹配)。

  5. 忽略某些成员:
    如果你只想绑定结构体或元组中的某些成员,而忽略其他的,可以使用 std::ignore (需要包含 <tuple> 头文件):

    cpp
    1
    2
    3
    Student s4("小李", 14);
    auto [studentNameOnly, std::ignore] = s4; // 忽略 age
    std::cout << "Only name: " << studentNameOnly << std::endl;
  6. 深入了解结构化绑定的实现机制(可选,较高级):
    如果你有兴趣,可以研究一下编译器大致是如何实现结构化绑定的。它通常涉及到匿名对象的创建或者直接访问成员,但具体的底层机制对于初学者来说可能比较复杂。

这些拓展问题将帮助你更全面地理解结构化绑定的强大功能和灵活性。继续保持这种学习的热情,你会在 C++ 的世界里发现更多有趣的东西!

如果你对这些拓展问题中的任何一个有疑问,或者想尝试后分享你的代码和发现,我随时都在这里!


评论