forked from balloonwj/CppGuide
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Change-Id: I350c41d90b39ece14b29e32bc3fd13e4a3ca708d
- Loading branch information
zhangyuanlong
committed
Oct 8, 2020
1 parent
26510f5
commit 81c07ed
Showing
588 changed files
with
42,867 additions
and
1 deletion.
There are no files selected for viewing
Submodule articles
deleted from
af1294
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
## C++ 17 结构化绑定 | ||
|
||
stl 的 map 容器很多读者应该都很熟悉,map 容器提供了一个 **insert** 方法,我们用该方法向 map 中插入元素,但是应该很少有人记得 **insert** 方法的返回值是什么类型,让我们来看一下 C++98/03 提供的 **insert** 方法的签名: | ||
|
||
``` | ||
std::pair<iterator,bool> insert( const value_type& value ); | ||
``` | ||
|
||
这里我们仅关心其返回值,这个返回值是一个 **std::pair** 类型,由于 map 中的元素的 key 不允许重复,所以如果 insert 方法调用成功,T1 是被成功插入到 map 中的元素的迭代器,T2 的类型为 bool,此时其值为 true(表示插入成功);如果 insert 由于 key 重复,T1 是造成 insert 插入失败、已经存在于 map 中的元素的迭代器,此时 T2 的值为 false(表示插入失败)。 | ||
|
||
在 C++98/03 标准中我们可以使用 **std::pair** 的 **first** 和 **second** 属性来分别引用 T1 和 T2 的值。如下面的我们熟悉的代码所示: | ||
|
||
``` | ||
#include <iostream> | ||
#include <string> | ||
#include <map> | ||
int main() | ||
{ | ||
std::map<std::string, int> cities; | ||
cities["beijing"] = 0; | ||
cities["shanghai"] = 1; | ||
cities["shenzhen"] = 2; | ||
cities["guangzhou"] = 3; | ||
//for (const auto& [key, value] : m) | ||
//{ | ||
// std::cout << key << ": " << value << std::endl; | ||
//} | ||
//这一行在 C++11 之前写法实在太麻烦了, | ||
//std::pair<std::map<std::string, int>::iterator, int> insertResult = cities.insert(std::pair<std::string, int>("shanghai", 2)); | ||
//C++ 11中我们写成: | ||
auto insertResult = cities.insert(std::pair<std::string, int>("shanghai", 2)); | ||
std::cout << "Is insertion successful ? " << (insertResult.second ? "true" : "false") | ||
<< ", element key: " << insertResult.first->first << ", value: " << insertResult.first->second << std::endl; | ||
return 0; | ||
} | ||
``` | ||
|
||
代码 **19** 行实在太啰嗦了,我们使用 auto 关键字让编译器自动推导类型。 | ||
|
||
**std::pair** 一般只能表示两个元素,C++11 标准中引入了 **std::tuple** 类型,有了这个类型,我们就可以放任意个元素了,原来需要定义成结构体的 POD 对象我们可以直接使用 **std::tuple** 表示,例如下面表示用户信息的结构体: | ||
|
||
``` | ||
struct UserInfo | ||
{ | ||
std::string username; | ||
std::string password; | ||
int gender; | ||
int age; | ||
std::string address; | ||
}; | ||
int main() | ||
{ | ||
UserInfo userInfo = { "Tom", "123456", 0, 25, "Pudong Street" }; | ||
std::string username = userInfo.username; | ||
std::string password = userInfo.password; | ||
int gender = userInfo.gender; | ||
int age = userInfo.age; | ||
std::string address = userInfo.address; | ||
return 0; | ||
} | ||
``` | ||
|
||
我们不再需要定义 struct UserInfo 这样的对象,可以直接使用 **std::tuple** 表示: | ||
|
||
``` | ||
int main() | ||
{ | ||
std::tuple<std::string, std::string, int, int, std::string> userInfo("Tom", "123456", 0, 25, "Pudong Street"); | ||
std::string username = std::get<0>(userInfo); | ||
std::string password = std::get<1>(userInfo); | ||
int gender = std::get<2>(userInfo); | ||
int age = std::get<3>(userInfo); | ||
std::string address = std::get<4>(userInfo); | ||
return 0; | ||
} | ||
``` | ||
|
||
从 **std::tuple** 中获取对应位置的元素,我们使用 **std::get** ,其中 N 是元素的序号(从 0 开始)。 | ||
|
||
与定义结构体相比,通过 **std::pair** 的 **first** 和 **second** 还是 **std::tuple** 的 **std::get** 方法来获取元素子属性,这些代码都是非常难以维护的,其根本原因是 **first** 和 **second** 这样的命名不能做到见名知意。 | ||
|
||
C++17 引入的**结构化绑定**(Structured Binding )将我们从这类代码中解放出来。**结构化绑定**使用语法如下: | ||
|
||
``` | ||
auto [a, b, c, ...] = expression; | ||
auto [a, b, c, ...] { expression }; | ||
auto [a, b, c, ...] ( expression ); | ||
``` | ||
|
||
右边的 **expression** 可以是一个函数调用、花括号表达式或者支持结构化绑定的某个类型的变量。例如: | ||
|
||
``` | ||
//形式1 | ||
auto [iterator, inserted] = someMap.insert(...); | ||
//形式2 | ||
double myArray[3] = { 1.0, 2.0, 3.0 }; | ||
auto [a, b, c] = myArray; | ||
//形式3 | ||
struct Point | ||
{ | ||
double x; | ||
double y; | ||
}; | ||
Point myPoint(10.0, 20.0); | ||
auto [myX, myY] = myPoint; | ||
``` | ||
|
||
这样,我们可以给用于绑定到目标的变量名(语法中的 **a**、**b**、**c**)起一个有意义的名字。 | ||
|
||
需要注意的是,绑定名称 **a**、**b**、**c** 是绑定目标的一份拷贝,当绑定类型不是基础数据类型时,如果你的本意不是想要得到绑定目标的副本,为了避免拷贝带来的不必要开销,建议使用引用,如果不需要修改绑定目标建议使用 const 引用。示例如下: | ||
|
||
``` | ||
double myArray[3] = { 1.0, 2.0, 3.0 }; | ||
auto& [a, b, c] = myArray; | ||
//形式3 | ||
struct Point | ||
{ | ||
double x; | ||
double y; | ||
}; | ||
Point myPoint(10.0, 20.0); | ||
const auto& [myX, myY] = myPoint; | ||
``` | ||
|
||
**结构化绑定**(Structured Binding )是 C++17 引入的一个非常好用的语法特性。有了这种语法,在遍历像 map 这样的容器时,我们可以使用更简洁和清晰的代码去遍历这些容器了: | ||
|
||
``` | ||
std::map<std::string, int> cities; | ||
cities["beijing"] = 0; | ||
cities["shanghai"] = 1; | ||
cities["shenzhen"] = 2; | ||
cities["guangzhou"] = 3; | ||
for (const auto& [cityName, cityNumber] : cities) | ||
{ | ||
std::cout << cityName << ": " << cityNumber << std::endl; | ||
} | ||
``` | ||
|
||
上述代码中 **cityName** 和 **cityNumber** 可以更好地反映出这个 map 容器的元素内容。 | ||
|
||
我们再来看一个例子,某 WebSocket 网络库(https://github.com/uNetworking/uWebSockets)中有如下代码: | ||
|
||
``` | ||
std::pair<int, bool> uncork(const char *src = nullptr, int length = 0, bool optionally = false) { | ||
LoopData *loopData = getLoopData(); | ||
if (loopData->corkedSocket == this) { | ||
loopData->corkedSocket = nullptr; | ||
if (loopData->corkOffset) { | ||
/* Corked data is already accounted for via its write call */ | ||
auto [written, failed] = write(loopData->corkBuffer, loopData->corkOffset, false, length); | ||
loopData->corkOffset = 0; | ||
if (failed) { | ||
/* We do not need to care for buffering here, write does that */ | ||
return {0, true}; | ||
} | ||
} | ||
/* We should only return with new writes, not things written to cork already */ | ||
return write(src, length, optionally, 0); | ||
} else { | ||
/* We are not even corked! */ | ||
return {0, false}; | ||
} | ||
} | ||
``` | ||
|
||
代码的第 **9** 行 **write** 函数返回类型是 **std::pair**,被绑定到 **[written, failed]** 这两个变量中去。前者在写入成功的情况下表示实际写入的字节数,后者表示是否写入成功。 | ||
|
||
``` | ||
std::pair<int, bool> write(const char *src, int length, bool optionally = false, int nextLength = 0) { | ||
//具体实现省略... | ||
} | ||
``` | ||
|
||
**结构化绑定的限制** | ||
|
||
结构化绑定不能使用 **constexpr** 修饰或被申明为 static,例如: | ||
|
||
``` | ||
//正常编译 | ||
auto [first, second] = std::pair<int, int>(1, 2); | ||
//无法编译通过 | ||
//constexpr auto [first, second] = std::pair<int, int>(1, 2); | ||
//无法编译通过 | ||
//static auto [first, second] = std::pair<int, int>(1, 2); | ||
``` | ||
|
||
注意:有些编译器也不支持在 lamda 表达式捕获列表中使用结构化绑定语法。 |
Oops, something went wrong.