CppGuide社区 CppGuide社区
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
🔥C++面试
  • 第1章 C++ 惯用法与Modern C++篇
  • 第2章 C++开发工具与调试进阶
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 网络通信协议设计
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 服务其他模块设计
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 🔥C++游戏编程入门(零基础学C++)
  • 🔥使用C++17从零开发一个调试器 (opens new window)
  • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
  • 🔥使用C++从零写一个C语言编译器 (opens new window)
  • 🔥从零用C语言写一个Redis
  • leveldb源码分析
  • libevent源码分析
  • Memcached源码分析
  • TeamTalk源码分析
  • 优质源码分享 (opens new window)
  • 🔥远程控制软件gh0st源码分析
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • 高效Go并发编程
  • Go性能调优
  • Go项目架构设计
  • 🔥使用Go从零开发一个数据库
  • 🔥使用Go从零开发一个编译器 (opens new window)
  • 🔥使用Go从零开发一个解释器 (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
🔥C++面试
  • 第1章 C++ 惯用法与Modern C++篇
  • 第2章 C++开发工具与调试进阶
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 网络通信协议设计
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 服务其他模块设计
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 🔥C++游戏编程入门(零基础学C++)
  • 🔥使用C++17从零开发一个调试器 (opens new window)
  • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
  • 🔥使用C++从零写一个C语言编译器 (opens new window)
  • 🔥从零用C语言写一个Redis
  • leveldb源码分析
  • libevent源码分析
  • Memcached源码分析
  • TeamTalk源码分析
  • 优质源码分享 (opens new window)
  • 🔥远程控制软件gh0st源码分析
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • 高效Go并发编程
  • Go性能调优
  • Go项目架构设计
  • 🔥使用Go从零开发一个数据库
  • 🔥使用Go从零开发一个编译器 (opens new window)
  • 🔥使用Go从零开发一个解释器 (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
  • 第1章C++惯用法与ModernC++篇

    • 1.1 C++ RAII惯用法
    • 1.2 pimpl 惯用法
    • 1.3 拥抱C++新变化(C++11/14/17新增的实用特性)
    • 1.4 统一的类成员初始化语法与 std::initializer_list<T>
    • 1.5 C++17注解标签(attributes)
    • 1.6 final/override/=default/=delete语法
    • 1.7 auto关键字的前尘后事
    • 1.8 Range-based 循环语法
    • 1.9 C++17结构化绑定
    • 1.10 stl容器新增的实用方法介绍
    • 1.11 stl中的智能指针类详解
  • 第2章C++开发工具与调试进阶

  • 第3章C++多线程编程从入门到进阶

  • 第4章C++网络编程重难点解析

  • 第5章网络通信故障排查常用命令

  • 第6章网络通信协议设计

  • 第7章高性能服务结构设计

  • 第8章Redis 网络通信模块源码分析

  • 第9章 服务其他模块设计

  • C++后端开发进阶
  • 第1章C++惯用法与ModernC++篇
zhangxf
2023-04-03

1.9 C++17结构化绑定

std::map容器很多读者应该都很熟悉,map容器提供了一个insert方法,我们用该方法向map中插入元素,但是应该很少有人记得insert方法的返回值是什么类型,让我们来看一下C++98/03提供的insert方法的签名:

std::pair<iterator,bool> insert( const value_type& value );
1

这里我们仅关心其返回值,这个返回值是一个**std::pair<T1, T2>**类型,由于map中元素的key不允许重复,所以如果insert方法调用成功,T1是被成功插入到map中的元素的迭代器,T2的类型为bool,此时其值为true(表示插入成功);如果insert由于key重复,T1是造成insert插入失败、已经存在于map中的元素的迭代器,此时T2的值为false(表示插入失败)。

在C++98/03标准中我们可以使用std::pair<T1, T2>的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;
}
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

代码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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

我们不再需要定义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;
}
1
2
3
4
5
6
7
8
9
10
11
12

从std::tuple中获取对应位置的元素,我们使用std::get<N> ,其中N是元素的序号(从0开始)。

与定义结构体相比,无论是通过std::pair的first和second还是std::tuple的std::get<N>方法来获取元素子属性,这些代码都是非常难以维护的,其根本原因是first和second这样的命名不能做到见名知意。

C++17引入的结构化绑定(Structured Binding )将我们从这类代码中解放出来。结构化绑定使用语法如下:

auto [a, b, c, ...] = expression;
auto [a, b, c, ...] { expression };
auto [a, b, c, ...] ( expression );
1
2
3

右边的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;
1
2
3
4
5
6
7
8
9
10
11
12
13

这样,我们可以给用于绑定到目标的变量名(语法中的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;
1
2
3
4
5
6
7
8
9
10

结构化绑定(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;
}
1
2
3
4
5
6
7
8
9
10

上述代码中cityName和cityNumber可以更好地反映出这个map容器的元素内容。

我们再来看一个例子,某 WebSocket 网络库中有如下代码:

//代码参见链接2
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};
        }
    }
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

代码的第10行write函数返回类型是std::pair<int, bool>,被绑定到**[written, failed]**这两个变量中去。前者在写入成功的情况下表示实际写入的字节数,后者表示是否写入成功。

std::pair<int, bool> write(const char *src, int length, bool optionally = false, int nextLength = 0) {
	//具体实现省略...
}
1
2
3

结构化绑定的限制

结构化绑定不能使用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);
1
2
3
4
5
6

有些编译器也不支持在lamda表达式捕获列表中使用结构化绑定语法。

上次更新: 2025/04/01, 20:53:14
1.8 Range-based 循环语法
1.10 stl容器新增的实用方法介绍

← 1.8 Range-based 循环语法 1.10 stl容器新增的实用方法介绍→

最近更新
01
C++语言面试问题集锦 目录与说明
03-27
02
第四章 Lambda函数
03-27
03
第二章 关键字static及其不同用法
03-27
更多文章>
Copyright © 2024-2025 沪ICP备2023015129号 张小方 版权所有
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式