C++17

基本语言特性

结构化绑定

概念:允许用一个对象的元素或成员同时实例化多个实体,形如:

绑定到map这个对象

map<int, string> m;
m.insert({
    
    1, "hello"});
m.insert({
    
    2, "world"});
m.insert({
    
    3, "good"});
for(const auto& [key, val]: m) {
    
    
  std::cout << key << ": " << val << "\n";
}

绑定到struct对象

struct MyStruct {
    
    
  int i = 0;
  std::string s;
};
MyStruct ms;
auto [u, v] = ms;

为了理解结构化绑定,必须意识到这里面其实有一个隐藏的匿名对象,结构化绑定时新引入的局部变量名其实都指向这个匿名对象的成员

auto [u, v] = ms;
auto e = ms;
aliasname u = e.i;
aliasname v = e.s;

使用修饰符
结构化绑定适用的场景

  • 对于所有非静态成员都是public的结构体和类,可以把每个成员绑定到一个新的变量名上
  • 对于原生数组,可以把数组的每个元素绑定到新的变量名上
  • 对于任何类型,可以使用tuple-like API来绑定新的名称,std::tuple, std::pair, std::array

注意:

  • 必须为结构化绑定使用auto关键字,例如,不能用int代替auto
  • 使用结构化绑定声明的变量数量必须与右侧表达式中的值数量匹配
  • 通常使用auto& 或者const auto&代替auto

if和switch初始化器

C++允许在if语句中包括一个初始化器,语法如下:

if(<initializer>; <conditional_expression>) {
    
     ... }
switch(<initializer>; <expression>) {
    
     ... }

if语句的条件表达式<initializer>里定义的变量将在整个if语句中有效,此类变量在if语句之外不可用

// 带初始化的if和switch语句
int b = 20;
if(int a = 10; b != a) {
    
    
    cout << a << endl;
    return;
}

可以在加锁的地方使用
内联变量
可以在头文件中以inline的方式定义全局变量
内联变量产生的动机:C++不允许在类里初始化非常量静态成员,可以在类定义的外部定义并初始化非常量静态成员,但如果被多个cpp文件同时包含的话又会引发新的错误,根据一次性定义原则,一个变量或实体的定义只能出现在一个编译单元内,除非该变量或实体被定义为inline
对于静态成员,在C++17中constexpr修饰符现在隐含着inline

static constexpr int n = 5;  // 等价于  
inline static constexpr int n = 5;
// 聚合体
struct Data {
    
    
    std::string name;
    double value;
};
// 聚合体初始化
Data x = {
    
    "test1", 6.778};
// 在C++11中起可以忽略等号
Data x {
    
    "test1", 6.778};
// 自C++17起聚合体可以拥有基类
struct MoreData : Data {
    
    
    bool done;
};
// 初始化时可以用如下两种方式:
MoreData y {
    
    {
    
    "test1", 6.778}, false };
MoreData y {
    
     "test1", 6.778, false };

std::string_view

头文件包含:#include<string_view>
应用场景:针对接收只读字符串的函数形参而言,如果是const char*的话,如果使用std::string,则必须调用其上的c_str()和data()来获取,但这样将失去std::string良好的面向对象的方面及其方法;如果改用std::string& 始终需要传入std::string。例如,传递一个字符串变量,编译器将默认创建一个临时字符串对象并将该对象传递给函数。所以有时需要重载多个版本,但这并不是一个很好的解决方案。
C++17中引入string_view解决了这类问题。
string_view基本就是const string&的简单替代品,但并不会复制字符串,不会产生开销。所以通常按值传递string_view,因为它的复制成本极低,只包含指向字符串的指针以及字符串的长度

  • 无法从string_view隐式构建一个string,可以使用string_view的data成员。同样,无法连接一个string和一个string_view
  • string_view不应该用于保存一个临时字符串的视图
string s {
    
    "hello"};
string_view sv {
    
     s + "world" };
cout << sv;
// 输出 elloworld
  • 可使用标准的用户定义的字面量sv,将字符串字面量解释为std::string_view。标准的用户定义字面量需要以下几条using命令之一:
    using namespace std::literals::string_view_literals;
    using namespace std::string_view_literals;

属性

[[nodiscard]]

鼓励编译器在某个函数的返回值未被使用时给出警告,但并不意味着编译器必须这么做

[[nodiscard]] int func() {
    
     return 42; }
int main() {
    
     func(); } 

以上代码编译器会发出告警
从C++20开始,可以以字符串的形式为[[nodiscard]]提供一个原因,例如:

[[nodiscard("Some explanation")]] int func();

在这里插入图片描述

[[maybe_unused]]

int func(int param1, int param2) {
    
     return 20; }

如果编译器告警级别设置的足够高,会报变量没有使用的警告。通过[[maybe_unused]]可以避免编译器在某个变量未被使用时发出警告。

void foo(int val, [[maybe_unused]] std::string str) {
    
    
    ...
}

class MyClass {
    
    
   char c;
   int i;
   [[maybe_unused]] char xxx[32];
};

[[maybe_unused]]属性可用于类和结构体,非静态数据成员,联合,typedef,类型别名,变量,函数,枚举以及枚举值。
注意:不能在一条语句上应用[[maybe_unused]]。
这个写法可以代替C或者以前通过 (void)var; 这种方式避免未使用的变量报告警的方法。

[[fallthrough]]

可以避免编译器在switch语句中某一个标签缺少break语句时发出警告

模板特性

constexpr if: 编译器if语句

新的标准库组件

std::optional<>

头文件:#include<optional>, 模拟了一个可以为空的任意类型的实例,可以被用作成员,参数,返回值等。如果想要允许值是可选的,则可以将optional用作函数的参数;如果函数可能返回也可能不返回某些内容,可以将optional用作函数的返回类型。这消除了从函数中返回”特殊“值的需要,如nullptr,end(), -1, EOF等。
同时定义了以下:

  • std::nullopt ,
  • 异常类std::bad_optional_access:派生自std::exception ,当无值的时候访问值将会出现异常。
  • std::in_place:可选对象也使用了<utility>头文件中定义的 std::in_place 对象来初始化多个参数的可选对象

操作:

  • 构造/析构/=
  • make_optional<>():创建一个用参数初始化的可选对象
  • has_value():判断一个optional是否有值,或者也可以简单的将optional用在if语句中。
  • value() :如果optional有值,可以用value或者解引用运算符访问
optional<int> getData(bool qiveIt) {
    
    
  if(qiveIt) {
    
     return 32; }
  return nullopt;
}
int main() {
    
    
    optional<int> data1 {
    
     getData(true) };
    optional<int> data2 {
    
     getData(false) };
    if(data1.has_value()) {
    
     std::cout << "data1: " << data1.value() << std::endl; }
    if(data2.has_value()) {
    
     std::cout << "data2: " << data2.value() << std::endl; }
}

std::variant<>

头文件:#include <variant>
说明:提供了一个新的联合类型,最大的优势是提供了一种新的具有多态性的处理异质集合的方法,也就是说可以处理不同类型的数据,并且不需要公共基类和指针
如果第一个类型没有默认构造函数,那么调用variant的默认构造函数将会导致编译期错误,为支持第一个参数没有默认构造的情况,C++标准库提供了std::monostate,可以作为第一个选项来保证variant能默认构造
可以从variant派生

std::any

头文件:#include<any>
说明:是一种在保证类型安全的基础上还能改变自身类型的值类型。也就是说,它可以持有任意类型的值,并且它知道当前持有的值是什么类型
std::any对象同时包含了值和值的类型。
为了将当前值转换为真实的类型,必须要使用any_cast<>,如果转换失败,可能是因为对象为空或者与内部值的类型不匹配,会抛出一个std::bad_any_cast 异常。
拷贝std::any的开销一般都很大,推荐以引用传递对象,或者move值,std::any支持部分move语义

std::byte

头文件:#include <cstddef>
在C++17之前使用char或者unsigned char来表示一个字节,但这些类型使得像是在处理字符。C++17提供了std::byte。这个类型代表内存的最小单位,std::byte本质上代表一个字节的值,但不能进行数字或字符的操作,也不对每一位进行解释
注意:std::byte实现和unsigned char类似,不能保证8位;底层实现的类型是unsigned char,所以大小总为1
列表初始化是唯一可以直接初始化std::byte对象的方法
没有定义输入和输出运算符,因此不得不把它转换为整数类型再进行IO

std::as_const

include<utility>,该方法接收一个引用参数,返回他的const引用版本。等价于 const_cast<const T&>(obj)

std::not_fn

否定器

#include<functional> 
template< class F>not_fn( F&& f );

内联变量

可将静态数据成员声明为inline,这样做的好处是不必在源文件中为它们分配空间,即不必再在源文件中做初始化定义

export class MyClass {
    
    
public:
  static inline int a {
    
     0 };
};

文件系统库

#include, 命名空间:std::filesystem,一个很常见的操作时定义:namespace fs = std::filesystem
异常:std::filesystem::filesystem_error

零星新特性

  1. 支持嵌套命名空间,如
namespace A {
    
    
  namespace B {
    
    
    namespace C {
    
    
	}
  }
}

可以写成:

namespace A::B::C {
    
    }
  1. std::size() 计算基于C风格数组的大小
int myArray[3] {
    
     2 };
std::size(myArray);

猜你喜欢

转载自blog.csdn.net/u010378559/article/details/131339680