文章

C++ 17特性

C++ 的结构化绑定 (Structured Binding)

结构化绑定 struct binding 是C++17的新特性,能让我们更好地处理多返回值。可以在将函数返回为tuplepairstruct等结构时且赋值给另外变量的时候,直接得到成员,而不是结构。

对于多个返回值,之前是用结构体去处理,而这个结构化绑定就是在这个的基础上拓展的一种新方法,特别是处理元组,对组(pairs)以及返回诸如此类的东西。

g++ 编译时需要加上 ‘-std=c++17’ or ‘-std=gnu++17’

老方法(tuple、pair)演示:

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

// std::pair<std::string,int> CreatPerson() // 只能有两个变量
std::tuple<std::string, int> CreatPerson() // 可以理解为pair的扩展
{
    return {"Cherno", 24};
}

int main()
{
    //元组的数据获取易读性差,还不如像结构体一样直接XXX.age访问更加可读。
    // std::tuple<std::string, int> person = CreatPerson();
     auto person = CreatPerson(); //用auto关键字
     std::string& name = std::get<0>(person);
     int age = std::get<1>(person);

    //tie 可读性好一点
     std::string name;
     int age;
     std::tie(name, age) = CreatPerson();
}

C++17 新方法:结构化绑定处理多返回值演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <string>
#include <tuple>


std::tuple<std::string, int> CreatPerson() 
{
    return {"Cherno", 24};
}

int main()
{
    auto[name, age] = CreatPerson(); //直接用name和age来储存返回值
    std::cout << name;
}

std::optional C++ 如何处理 optional 数据

C++17 在 STL 中引入了 std::optional,就像 std::variant 一样,std::optional 是一个 “和类型 (sum type)“,也就是说,std::optional 类型的变量要么是一个 T 类型的变量,要么是一个表示 “ 什么都没有 “ 的状态

std::optional 是 C++17 的新东西,用于检测数据是否存在 or 是否是我们期盼的形式,用于处理那些可能存在,也可能不存在的数据 or 一种我们不确定的类型 。

std::optional 提供的常见方法 :

  • has_value():判断对应的optional 是否处于已经设置值的状态
1
2
3
4
5
6
7
8
9
10
11
int main()
{
	std::string text = /*...*/;
	std::optional<unsigned> opt = firstEvenNumberIn(text);
	if (opt.has_value())  //直接if(opt)即可,代码更简洁
	{
	 std::cout << "The first even number is "
	           << opt.value()
	           << ".\n";
	}
}
  • 访问 optional 对象中的数据
1
2
3
4
5
1. opt.value()
2. (*opt)
3. value_or() //value_or()可以允许传入一个默认值, 如果optional为std::nullopt, 
              //则直接返回传入的默认值.(如果数据确实存在于std::optional中,
              //它将返回给我们那个字符串。如果不存在,它会返回我们传入的任何值)

示例:比如在读取文件内容的时候,往往需要判断读取是否成功,常用的方法是传入一个引用变量或者判断返回的 std::string 是否为空,C++17 引入了一个更好的方法,std::optional

老方法:传入一个引用变量或者判断返回的 std::string 是否为空

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <fstream>
#include <string>
std::string ReadFile(const std::string &fileapath, bool &outSuccess) {
    std::ifstream stream(filepath);
    //如果成功读取文件
    if (stream) {
        std::string result;
        getline(stream,result);
        stream.close();
        outSuccess = true;  //读取成功,修改bool
        return result;
    }
    outSuccess = false; //反之
}
int main() {
    bool flag;
    auto data = ReadFile("data.txt", flag);
    //如果文件有效,则接着操作
    if (flag) {
    }
}

新方法:std::optional:

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
// 用g++编译时需要加上‘-std=c++17’ or ‘-std=gnu++17’
// std::optional同样是C++17的新特性,可以用来处理可能存在、也可能不存在的数据

//data.txt在项目目录中存在,且其中的内容为"data!"

#include <iostream>
#include <fstream>
#include <optional>
#include <string>

std::optional<std::string> ReadFileAsString(const std::string& filepath)
{
    std::ifstream stream(filepath);
    if (stream)
    {
        std::string result;
        //getline(stream, result);
        stream.close();
        return result;
    }

    return {}; //返回空
}

int main()
{
    std::optional<std::string>  data = ReadFileAsString("data.txt");

    std::string value = data.value_or("Not present");
    std::cout << value << std::endl;

    if (data)
    {
        std::cout << "File read successfully!" << std::endl;
    }
    else
    {
        std::cout << "File could not be opened!" << std::endl;
    }
}
//输出
// Not present
// File could not be opened!

如果文件无法打开,或者文件的特定部分没有被设置或读取,也许我们有一个默认值,这很常见。此时就可以使用 value_or() 函数。其作用就是:如果数据确实存在于 std::optional 中,它将返回给我们那个字符串。如果不存在,它会返回我们传入的任何值。

删除 data.txt,此时文件不存在打不开,则被设置为默认值

std::variant C++ 单一变量存放多种类型的数据

  • std::variantC++17 的新特性,可以让我们不用担心处理的确切数据类型,是一种可以容纳多种类型变量的结构。

它和 option 很像,它的作用是让我们不用担心处理确切的数据类型,只有一个变量,之后我们在考虑它的具体类型

故我们做的就是指定一个叫 std::variant 的东西,然后列出它可能的数据类型

  • union 的区别

1)union 中的成员内存共享。union 更有效率。 2) std::variant 的大小是 <> 里面的大小之和。variant更加类型安全,不会造成未定义行为,所以应当去使用它,除非做的是底层优化,非常需要性能

示例:

1
2
3
4
5
6
7
8
9
std::variant<string, int> data; //列举出可能的类型
data = "hello";
// 索引的第一种方式:std::get,但是要与上一次赋值类型相同,不然会报错
cout << std::get<string>(data) <<endl;//print hello
data = 4;
cout << std::get<int>(data) <<endl;//print 4
cout << std::get<string>(data) <<endl;//编译通过,但是runtime会报错,显示std::bad_variant_access
data = false;//能编译通过
cout << std::get<bool>(data) <<endl;//这句编译失败
  • index() 索引
1
2
3
std::variant<string, int> data; //列举出可能的类型
//std::variant的index函数
data.index();// 返回一个整数,代表data当前存储的数据的类型在<>里的序号,比如返回0代表存的是string, 返回1代表存的是int
  • get_if()
1
2
3
4
5
6
// std::get的变种函数,get_if
auto p = std::get_if<std::string>(&data);//p是一个指针,如果data此时存的不是string类型的数据,则p为空指针,别忘了传的是地址
// 如果data存的数据是string类型的数据
if(auto p = std::get_if<string>(&data)){
    string& s = *p;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//参考:https://zhuanlan.zhihu.com/p/352420950
#include<iostream>
#include<variant>
int main()
{
    std::variant<std::string,int> data; // <>里面的类型不能重复
    data = "ydc";
    // 索引的第一种方式:std::get,但是要与上一次赋值类型相同,不然会报错
    std::cout<<std::get<std::string>(data)<<std::endl;
    // 索引的第二种方式,std::get_if,传入地址,返回为指针
    if (auto value = std::get_if<std::string>(&data))
    {
        std::string& v = *value;
    }
    data = 2;
    std::cout<<std::get<int>(data)<<std::endl;
    std::cin.get();
}

std::any C++ 如何存储任意类型的数据

  • 是 C++17 引入的可以存储多种类型变量的结构,其本质是一个 union,但是不像 std::variant 那样需要列出类型。使用时要包含头文件 #include <any>
  • 对于小类型 (small type) 来说,any 将它们存储为一个严格对齐的 union,对于大类型,会用 void*,动态分配内存
  • 评价:基本无用。 当在一个变量里储存多个数据类型,用 any 的类型安全版本即可:variant
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <any>
// 这里的new的函数,是为了设置一个断点,通过编译器观察主函数中何处调用了new,看其堆栈。
void *operator new(size_t size)
{
    return malloc(size);
}

int main()
{
    std::any data;
    data = 2;
    data = "Cherno";
    data = std::string("Cherno");

    std::string& string = std::any_cast<std::string&>(data); //用any_cast指定转换的类型,如果这个时候any不是想要转换的类型,则会抛出一个类型转换的异常
    // 通过引用减少复制操作,以免影响性能
}
本文由作者按照 CC BY 4.0 进行授权