2023-4-6-C++历届版本新特性系统全面的学习!!!(全面准确,建议关注收藏订阅专栏)



*★,°*:.☆( ̄▽ ̄)/$:*.°★*

欢迎来到汤姆的csdn博文
喜欢的朋友可以关注一下,下次更新不迷路
私聊获取个人订阅号哦,欢迎订阅共同学习
可以加入大家庭群聊,一起学习天天有福利





本文摘要

在这里插入图片描述
对于想要成为一名资深C++工程师的我来说,对于C++历代版本更新的了解是不可或缺的,每一个版本增加的新特性都是C++这门语言进化的成果。如果你问我为什么喜欢C++这门语言,我可以告诉你的是,C++这门语言它虽然不完美,有很多让人诟病的缺点,但是它一直在成长,一直在进化,一直在变得越来越好,在保持自己的优点的前提下努力的学习其它语言的优点,争取让自己变得越来越好,就好像一个刚刚踏入编程生涯的小白程序员,只有通过不断地学习才能一点一点逐渐的成长,变得更加的健壮,所以,我爱C++因为它如此的真实。这篇文章我会花大量的时间和巨长的篇幅带来一份尽可能详尽的系统的C++历代版本的演变和新特性的介绍,希望能够让读者读完后有所收获,欢迎点赞评论收藏加订阅呀。


目录

  • 本文摘要
  • 一、C++历史版本编年史
  • ‍二、C++11新特性
    • auto自动类型推导
    • decltype
    • 左值右值
    • 列表初始化
    • std::function
    • std::bind
    • lambda表达式
    • 模板的改进
    • 并发
    • 智能指针
    • 基于范围的for循环
    • 委托构造函数
    • 继承构造函数
    • nullptr
    • final
    • override
    • default
    • delete
    • explicit
    • const
    • constexpr
    • enum class
    • 非受限联合体
    • sizeof
    • 自定义字面量
    • 内存对齐
    • thread_local
    • 基础数值类型
    • 随机数功能
    • 正则表达式
    • chrono
    • duration
    • time_point
    • clocks
    • steady_clock
    • system_clock
    • 新增数据结构
    • 新增算法
  • 三、C++14新特性
  • ‍四、C++17新特性
  • 五、C++20新特性
  • 参考文献
  • 文章总结



一、C++历史版本编年史

年份 C++标准 通用名
1978 C with Classes -
1998 ISO/IEC 14882:1998 C++98
2003 ISO/IEC 14882:2003 C++03
2011 ISO/IEC 14882:2011 C++11
2014 ISO/IEC 14882:2014 C++14
2017 ISO/IEC 14882:2017 C++17
2020 ISO/IEC 14882:2020 C++20
2023 waiting waiting

小知识
什么是C++0X:上一个版本的C++国际标准是2003年发布的,所以叫C++ 03。然后C++国际标准委员会在研究C++ 03的下一个版本的时候,一开始计划是07年发布,所以最初这个标准叫C++ 07。但是到06年的时候,官方觉得07年肯定完不成C++ 07,而且官方觉得08年可能也完不成。最后干脆叫C++ 0x。x的意思是不知道到底能在07还是08还是09年完成。结果2010年的时候也没完成,最后在2011年终于完成了C++标准。所以最终定名为C++11。

小知识
C++的简单由来:
1998年是C++标准委员会成立的第一年,以后每5年视实际需要更新一次标准。
2009年,C++标准有了一次更新,一般称该草案为C++0x。
C++0x是C++11标准成为正式标准之前的草案临时名字。
后来,2011年,C++新标准标准正式通过,更名为ISO/IEC 14882:2011,简称C++11。

小知识
C++11后的进化史:

  • C++11,先前被称作C++0x,即ISO/IEC 14882:2011,是C++编程语言的一个标准。
    它取代第二版标准ISO/IEC 14882:2003 (第一版ISO/IEC 14882:1998公开于1998年, 第二版于2003年更新,分别通称C++98以及C++03,两者差异很小),且已被C++14取代
  • C++14 旨在作为C++11的一个小扩展,主要提供漏洞修复和小的改进。
    2014年8月18日,经过C++标准委员投票,C++14标准获得一致通过。ISO/IEC 14882:2014
  • C++17 又称C++1z,是继 C++14 之后,C++ 编程语言 ISO/IEC 标准的下一次修订的非正式名称。
    官方名称 ISO/IEC 14882:2017,基于 C++ 11,C++ 17 旨在简化该语言的日常使用,使开发者可以更简单地编写和维护代码。
    -C++20在2020年更新,用C++之父的话说C++20目的就是stability–稳定(稳定压到一切)和
    evolution–更新(与时俱进,走到潮流之巅)

‍二、C++11新特性

auto自动类型推导

  • auto:让编译器在编译器就推导出变量的类型,可以通过=右边的类型推导出变量的类型,类似于C#之中的var。可以用它来躲开一堆长长的类型名,比如STL容器的iterator。
  • auto的自动类型推断发生在编译期,所以使用auto并不会造成程序运行时效率的降低。而是否会造成编译期的时间消耗,我认为是不会的,在未使用auto时,编译器也需要得知右操作数的类型,再与左操作数的类型进行比较,检查是否可以发生相应的转化,是否需要进行隐式类型转换。
  • 使用auto会让你的代码变得容易维护,上边例子中,for循环里所有变量的类型都是由b推导出来了。如果有人突然觉得这里int太小了,要改为unsigned long呢?如果你像上边那样写,你需要做的事情无非就是把int改为unsigned long——其他变量的类型变化就交给编译器和auto来处理好了i会自动变为unsigned long类型。
#include "iostream"
#include "vector"

auto func() { //函数的返回值可以用auto自动推导
    auto b = std::vector<int>{1, 2, 3, 4};
    for (auto i: b) {//auto还可以这么用
        std::cout << i << std::endl;
    }
}

int main() {
    auto a = 10;//无需显式的声明int a=10,编译器在运行的时候会自动推导。

    return 0;
};

注意事项:

  • auto 变量必须在定义时初始化,这类似于const关键字。
int main() {
 auto a=0;
 auto b;//不进行初始化会报错
};
  • 定义在一个auto序列的变量必须始终推导成同一类型。
int main() {
 auto a=10,b=10.0;//一个是int一个是非int类型,无法正确推导
};
  • 如果初始化表达式是引用,则去除引用语义。
#include "iostream"
#include "vector"

int main() {
    int a = 10;
    int &b = a;
    auto c = b;
    auto &d = b;
    c = 100;
    std::cout << a << std::endl;//输出为100
    d = 1000;
    std::cout << a << std::endl;//输出为1000
    std::cout << typeid(c).name() << std::endl;//输出为int
    std::cout << typeid(d).name() << std::endl;//输出为int
    std::cout << &a << std::endl;//输出为0000003225CFFCB4
    std::cout << &b << std::endl;//输出为0000003225CFFCB4
    std::cout << &c << std::endl;//输出为0000003225CFFCD4
    std::cout << &d << std::endl;//输出为0000003225CFFCB4
};
  • 如果初始化表达式为const或volatile(或者两者兼有),则除去const/volatile语义。
#include "iostream"
#include "vector"

int main() {
    const int a1 = 10;
    auto  b1= a1; //b1的类型为int而非const int(去除const)
    const auto c1 = a1;//此时c1的类型为const int
    b1 = 100;//合法
    c1 = 100;//非法
};
  • 如果auto关键字带上&号,则不去除const语意。
#include "iostream"
#include "vector"

int main() {
    const int a2 = 10;
    auto &b2 = a2;//因为auto带上&,故不去除const,b2类型为const int
    std::cout<< typeid(a2).name()<<std::endl;//int 为const int
    std::cout<< typeid(b2).name()<<std::endl;//int 为const int
    b2=1;//非法
};
  • 初始化表达式为数组时,auto关键字推导类型为指针。若表达式为数组且auto带上&,则推导类型为数组类型。
#include "iostream"
#include "vector"

int main() {
    int a[3] = {1, 2, 3};
    auto b = a;
    auto &c = a;
    std::cout << typeid(a).name() << std::endl;//int [3]
    std::cout << typeid(b).name() << std::endl;//int *__ptr64
    std::cout << typeid(c).name() << std::endl;//int [3]

};
  • 函数或者模板参数不能被声明为auto
void func(auto a)  //错误
{
    //... 
}
  • 时刻要注意auto并不是一个真正的类型。
    auto仅仅是一个占位符,它并不是一个真正的类型,不能使用一些以类型为操作数的操作符,如sizeof或者typeid。
  cout << sizeof(auto) << endl;//错误
    cout << typeid(auto).name() << endl;//错误

decltype

decltype则用于推导表达式类型,这里只用于编译器分析表达式的类型,表达式实际不会进行运算,decltype不会像auto一样忽略引用和cv属性,decltype会保留表达式的引用和cv属性

对于decltype(exp)有

  • exp是表达式,decltype(exp)和exp类型相同
  • exp是函数调用,decltype(exp)和函数返回值类型相同
  • 其它情况,若exp是左值,decltype(exp)是exp类型的左值引用
#include "iostream"
#include "vector"
#include "functional"

void *fun() {
    return nullptr;
}

int main() {
    int a = 3;
    decltype(a) b = 0;
    decltype(fun()) c;//
    decltype(a + b) d = 0;//d是int,因为(a+b)返回一个右值
    decltype(a += b) e = d;//e是int&,因为(a+=b)返回一个左值
    std::cout << typeid(a).name() << std::endl;//int
    std::cout << typeid(b).name() << std::endl;//int
    std::cout << typeid(c).name() << std::endl;//void * __ptr64
    std::cout << typeid(d).name() << std::endl;//int
    std::cout << typeid(e).name() << std::endl;//int

    std::cout << d << std::endl;//0
    e = 5;
    std::cout << d << std::endl;//5 因为e是int&

};

小知识
decltype如何配合auto使用
auto和decltype一般配合使用在推导函数返回值的类型问题上,
例如
template decltype(t + u) add(T t, U u) { // t和u尚未定义 return t + u; }
这段代码在C++11上是编译不过的,因为在decltype(t +u)推导时,t和u尚未定义,就会编译出错,所以有了下面的叫做返回类型后置的配合使用方法:
template auto add(T t, U u) -> decltype(t + u) { return t + u; }
返回值后置类型语法就是为了解决函数返回制类型依赖于参数但却难以确定返回值类型的问题。
2023-4-6-C++历届版本新特性系统全面的学习!!!(全面准确,建议关注收藏订阅专栏)_第1张图片
2023-4-6-C++历届版本新特性系统全面的学习!!!(全面准确,建议关注收藏订阅专栏)_第2张图片
编译器自动判断了返回值类型,牛啊!

左值右值

这个知识点请跳转我另一篇文章,讲的非常的清楚明白
2023-3-9-一篇简短的文章把C++左右值关系讲的透透彻彻

列表初始化

C++11新增了列表初始化的概念。
在C++11中可以直接在变量名后面加上初始化列表来进行对象的初始化。

#include "iostream"
#include "vector"
#include "functional"

int main() {
    std::vector a {1, 2, 3, 4, 5, 6};
};

列表初始化的一些规则:

首先说下聚合类型可以进行直接列表初始化,这里需要了解什么是聚合类型:

  1. 类型是一个普通数组,如int[5],char[],double[]等
  2. 类型是一个类,且满足以下条件:
    没有用户声明的构造函数
    没有用户提供的构造函数(允许显示预置或弃置的构造函数)
    没有私有或保护的非静态数据成员
    没有基类
    没有虚函数
    没有{}和=直接初始化的非静态数据成员
    没有默认成员初始化器
#include "iostream"
#include "vector"
#include "functional"

class A{
public:
    int a;//如果为private则错误
};

int main() {
    A a{0};
};
struct A {
    int a;
    int b;
    int c;
    A(int, int){}
};
int main() {
    A a{1, 2, 3};// error,A有自定义的构造函数,不能列表初始化
}
struct A {
    int a;
    int b;
    virtual void func() {} // 含有虚函数,不是聚合类
};

struct Base {};
struct B : public Base { // 有基类,不是聚合类
      int a;
    int b;
};

struct C {
    int a;
    int b = 10; // 有等号初始化,不是聚合类
};

struct D {
    int a;
    int b;
private:
    int c; // 含有私有的非静态数据成员,不是聚合类
};

struct E {
      int a;
    int b;
    E() : a(0), b(0) {} // 含有默认成员初始化器,不是聚合类
};

上面列举了一些不是聚合类的例子,对于一个聚合类型,使用列表初始化相当于对其中的每个元素分别赋值;对于非聚合类型,需要先自定义一个对应的构造函数,此时列表初始化将调用相应的构造函数。

小知识
我们平时开发使用STL过程中可能发现它的初始化列表可以是任意长度,大家有没有想过它是怎么实现的呢,答案是std::initializer_list,看下面这段示例代码:
struct CustomVec { std::vector data; CustomVec(std::initializer_list list) { for (auto iter = list.begin(); iter != list.end(); ++iter) { data.push_back(*iter); } } };
std::initializer_list,它可以接收任意长度的初始化列表,但是里面必须是相同类型T,或者都可以转换为T。

std::function

std::function是可调用对象的封装器,可以把std::function看做一个函数对象,用于表示函数这个抽象概念。std::function的实例可以存储、复制和调用任何可调用对象,存储的可调用对象称为std::function的目标,若std::function不含目标,则称它为空,调用空的std::function的目标会抛出std::bad_function_call异常。

小知识
什么是可调用对象
满足以下条件之一就可称为可调用对象:
是一个函数指针
是一个具有operator()成员函数的类对象(传说中的仿函数),lambda表达式
是一个可被转换为函数指针的类对象
是一个类成员(函数)指针
bind表达式或其它函数对象

#include "iostream"
#include "vector"
#include "functional"

class A {
public:
    A(int a) {}

    void print_a() {
        std::cout << a << std::endl;
    }

public:
    int a = 4;
};

void print_num(int i) {
    std::cout << i << std::endl;
}

int main() {
    //存储自由函数
    std::function<void(int)> function = print_num;
    function(1);//输出1
    //存储lambda
    std::function<void(int)> function1 = [](int i) {
        std::cout << i << std::endl;
    };
    function1(2);//输出2
    //存储到 std::bind 调用的结果
    std::function < void() > function2 = std::bind(print_num, 3);
    function2();//输出3 如果function2为void(int)类型,那么该函数传的参数不会影响结果,实际的参数是bind绑定的参数
    //存储到成员函数的调用
    std::function<void(A)> function3 = &A::print_a;
    function3(A{4});//输出4
};
   

当给std::function填入合适的参数表和返回值后,它就变成了可以容纳所有这一类调用方式的函数封装器。std::function还可以用作回调函数,或者在C++里如果需要使用回调那就一定要使用std::function,特别方便。

std::bind

使用std::bind可以将可调用对象和参数一起绑定,最主要的功能就是实现延迟调用

  • 将可调用对象与参数一起绑定为另一个std::function供调用
  • 将n元可调用对象转成m(m < n)元可调用对象,绑定一部分参数,这里需要使用std::placeholders
#include "iostream"
#include "vector"
#include "functional"
#include 

void print_num(int i,int k,int j) {
    std::cout << i << std::endl;
}

int main() {
    //存储自由函数
    std::function<void(int,int)> function = std::bind(print_num,5,std::placeholders::_1,std::placeholders::_2);
    function(2,3);//输出1
};

lambda表达式

lambda表达式可以说是c++11引用的最重要的特性之一,它定义了一个匿名函数,可以捕获一定范围的变量在函数内部使用,一般有如下语法形式:

auto func = [capture] (params) opt -> ret { func_body; };

其中func是可以当作lambda表达式的名字,作为一个函数使用,capture是捕获列表,params是参数表,opt是函数选项(mutable之类), ret是返回值类型,func_body是函数体。
一个完整的lambda表达式:

auto func1 = [](int a) -> int { return a + 1; };
auto func2 = [](int a) { return a + 2; };
cout << func1(1) << " " << func2(2) << endl;

如上代码,很多时候lambda表达式返回值是很明显的,c++11允许省略表达式的返回值定义。

lambda表达式允许捕获一定范围内的变量:

  • []不捕获任何变量
  • [&]引用捕获,捕获外部作用域所有变量,在函数体内当作引用使用
  • [=]值捕获,捕获外部作用域所有变量,在函数内内有个副本使用
  • [=, &a]值捕获外部作用域所有变量,按引用捕获a变量
  • [a]只值捕获a变量,不捕获其它变量
  • [this]捕获当前类中的this指针
#include "iostream"
#include "vector"
#include "functional"


int main() {
    //值捕获
    int a = 0;
    std::function < void() > function = [=]() -> void {
        a=10;//报错
        std::cout << a << std::endl;//0
    };
    function();
    std::cout << a << std::endl;//0
    //引用捕获
    std::function < void() > function1 = [&]() -> void {
        a=10;
        std::cout << a << std::endl;//10
    };
    function1();
    std::cout << a << std::endl;//10
};

代码中的a=10是编译不过的,因为我们修改了按值捕获的外部变量,其实lambda表达式就相当于是一个仿函数,仿函数是一个有operator()成员函数的类对象,这个operator()默认是const的,所以不能修改成员变量,而加了mutable,就是去掉const属性。

#include "iostream"
#include "vector"
#include "functional"


int main() {
    //值捕获加mutable 
    int a = 0;
    std::function < void() > function = [=]() mutable -> void {
        a=10;//此时不报错
        std::cout << a << std::endl;//10
    };
    function();
    std::cout << a << std::endl;//0
};

模板的改进

  • 模板的右尖括号

    C++11之前是不允许两个右尖括号出现的,会被认为是右移操作符,所以需要中间加个空格进行分割,避免发生编译错误。

  • 模板的别名
    C++11引入了using,可以轻松的定义别名,而不是使用繁琐的typedef。

    typedef std::vector<std::vector<int>> vvi; // before c++11
    using vvi = std::vector<std::vector<int>>; // c++11
    
  • 函数模板的默认模板参数
    C++11之前只有类模板支持默认模板参数,函数模板是不支持默认模板参数的,C++11后都支持。

并发

c++11引入了std::thread来创建线程,支持对线程join或者detach

#include "iostream"
#include "thread"
#include "memory"
#include "functional"

int main() {

    std::function < void() > func = []() {
        for (int i = 0; i < 10; i++) {
            std::cout << i << " ";
        }
        std::cout << std::endl;
    };
    std::thread t(func);
    if(t.joinable())
    {
        t.detach();//使用detach()函数会让线程在后台运行,即说明主线程不会等待子线程运行结束才结束通常称分离线程为守护线程(daemon threads),UNIX中守护线程是指,没有任何显式的用户接口,并在后台运行的线程。这种线程的特点就是长时间运行;线程的生命周期可能会从某一个应用起始到结束,可能会在后台监视文件系统,还有可能对缓存进行清理,亦或对数据结构进行优化。另一方面,分离线程的另一方面只能确定线程什么时候结束,发后即忘(fire andforget)的任务就使用到线程的这种方式
    }
    std::function < void(int) > func1 = [](int k) {
        for (int i = 0; i < k; i++) {
            std::cout << i << " ";
        }
        std::cout << std::endl;
    };
    std::thread tt(func1, 20);
    if(tt.joinable())
    {
        tt.join();//join()函数是一个等待线程完成函数,主线程需要等待子线程运行结束了才可以结束
    }
    return 0;
}



std::mutex是一种线程同步的手段,用于保存多线程同时操作的共享数据。
mutex分为四种:

  • std::mutex:独占的互斥量,不能递归使用,不带超时功能
  • std::recursive_mutex:递归互斥量,可重入,不带超时功能
  • std::timed_mutex:带超时的互斥量,不能递归
  • std::recursive_timed_mutex:带超时的互斥量,可以递归使用
#include "iostream"
#include "thread"
#include "mutex"
#include "functional"
std::timed_mutex mutex;

int main() {

    std::function < void(int) > func1 = [](int k) {
        mutex.try_lock_for(std::chrono::microseconds (1));
        for (int i = 0; i < k; i++) {
            std::cout << i << " ";
        }
        std::cout << std::endl;
        mutex.unlock();
    };
    std::vector<std::thread> threads;
    for(int i=0;i<5;i++)
    {
        threads.emplace_back(std::thread(func1,200));
    }
    for(auto& i:threads)
    {
        if(i.joinable())
        {
            i.join();
        }
    }

    return 0;
}
//因为只加了一微秒的锁,锁一解开就开始出现线程抢占资源的现象
//0 1 2 3 4 5 6 7 8 9 10 11 120 13 14  115  216 3 17 4  518 6 19 7 20 21  228  923 24 25 26 27 28 29 30 31 32 33 34 35 36 370 1  380 39  1 2 3 4  510 60  11140  7 122 2 3  48   9 5 31013 11 14   1512 441  426  743   8 44 5 
//4513 14  15 4616 17 47  18 1948 20  4921 50 51  52 22 23 24916 10  1117  18 19 20  2125  22 626 7 27 8 912 10 53  54 5523 24   2528 26   272911 3013 12  1413 15 14 1656 17  57  5831 1815 19  20  2128   3259  221629 17 30 18  31 1933 20    2334 3235 33 60 36  3461 213524 36 25 37   38 26 39 27 40  284122 29   30 31 3237 33  34 35 36  3742 3823 39 2462 25  63 26 6443 38 65 44  4566 46  674039 41  4047 48 49 50  5168 52 69  5327 54 28   55  2942 30 43 44 31 32  334541  4670 4742  48  49  5034 51 35 3656 37  5738   5839 5971 60  6172 62 7343 44  45 4046 41  47 42   4348 7452 75  5376 77 54 55 63  6444   4549  5056  65 57 66 58 5946 4751 48  52 4978 50  5179  526067 61  68   6953 70  5362 54 55 56  5463 5580 56  5781  5782 58   597158 72   7364 74 75 65 7683  77 60  7866 79  6784 68 59 69  60 70 61 71 72 6261 63  646285 6380 64 73  7465  75  8681  82  876683 88   8984 7667 77 65 78 90 79  80  8166  8285 83 84 85 86  8786 88 8991 90 91 92  6892 69   937093 94  9567  876896 69  8870 89 90  91 92  93 9471 95  72 9671  9472 95 73   7497  9798   9899  9973100  101 100102 101 103   104 105102 106 103 107  75108  109 110 11176 112  113 7774 114 115104 116  117105  78106118 107 119  10896 97  9879   9912075 12180 122  123  12481 12576   12677 127   12882 129 78  13079  131100 132 83 133 84101 85  10286   87109 110  111 112 113103 10480 105  106 81107  108134 109   135 11482 115  83 13688 137  138 13989 140 141  14284 143  144110 145 146   111147116 148 85 8690 87  88  8991 11290 113117 114   115 116   1179192 92  93149 94 95 150 96  151118118 97  93119  11994 120 98120   121 99152  10015395 154  155  15696  121 122 123  124 125 157101 122 102  12310397 104 98  105  126106 127  107  108124158 125  159126 99160  100128 101 102  103  104129  130105 131 161132 162 109133 110  111106  127163 128 164  165  166129 167134   168112 113135 114 136  137115  116 117 107   169138 170  171 172118 173130 174 131   132139 133 140  134 119175 120 176  177 178121 179 122 180 123 181141 182  183108 109  110 184142 185  186 143124  144 125111 126 112   113 127145 128187  146  188129147 189 148   190114  191 192135 193130 194 131 195149 132   133196 134   135136115 137  138150 151  152 153 154 155  156 197  198139116 140  141 142117 143 118 119157 158  159 199136  137 
// 120160 121 161  162 122144 138 145 139 123 146  163124 164 125 126 165  //140147166  127148 128  149 129  130167 131 150132  133151 134 152 135 153 136 137  154141   168142  169 143 170 144 155 138 156 139171 140  141145 142  143146  144 147157 172 145 158   146148 149159 150 151 160 173161 162  163174 164 165 166  167  152  153175168  169  170 147171 148154  149172 155176 156   157177  178 150179 180 151 158152  153 159154  160173  174181  175161  162176  177  178 155163 156 164   157165179 166182 167  168  169158   159183 160  161 184180 185  186170 171162 172  173163181 164  165182  183 166 174187 175 176  177167    168178188 169  179184 189 185  190 186170   180191 181171 182 187172 188   192173 183 193 184 194 185 195  186 189196174 190  197   187175198  188 199 189191  190 176
//191  192192 193  193194   177195 178  194179 195 180 196196 197 198  181199  182
//  183197  184198  185199  186
// 187 188 189 190 191 192 193 194 195 196 197 198 199 

std::lock相关
可以动态的释放锁资源,防止线程由于编码失误导致一直持有锁,c++11主要有std::lock_guard和std::unique_lock两种方式,使用方式都类似。

小知识
lock_guard 类是一个mutex封装者,它为了拥有一个或多个mutex而提供了一种方便的 RAII style 机制。( 译注:所谓的RAII,全称为Resource Acquisition Is Initialization,汉语是“资源获取即初始化”。但是这个直译并没有很好地解释这个词组的含义。其实含义并不高深复杂,简单说来就是,在资源获取的时候将其封装在某类的object中,利用"栈资源会在相应object的生命周期结束时自动销毁"来自动释放资源,即,将资源释放写在析构函数中。所以这个RAII其实就是和智能指针的实现是类似的。)。
当一个lock_guard对象被创建后,它就会尝试去获得给到它的mutex的所有权。当控制权不在该lock_guard对象所被创建的那个范围后,该lock_guard就会被析构,从而mutex被释放。
定义lock_guard的时候调用构造函数加锁,大括号解锁的时候调用析构函数解锁。虽然lock_guard挺好用的,但是有个很大的缺陷,在定义lock_guard的地方会调用构造函数加锁,在离开定义域的话lock_guard就会被销毁,调用析构函数解锁。这就产生了一个问题,如果这个定义域范围很大的话,那么锁的粒度就很大,很大程序上会影响效率。因此就引入了unique_lock

小知识
unique_lock<> unique();
这个会在构造函数加锁,然后可以利用unique.unlock()来解锁,所以当你觉得锁的粒度太多的时候,可以利用这个来解锁,而析构的时候会判断当前锁的状态来决定是否解锁,如果当前状态已经是解锁状态了,那么就不会再次解锁,而如果当前状态是加锁状态,就会自动调用unique.unlock()来解锁。而lock_guard在析构的时候一定会解锁,也没有中途解锁的功能。当然,方便肯定是有代价的,unique_lock内部会维护一个锁的状态,所以在效率上肯定会比lock_guard慢。

#include "iostream"
#include "thread"
#include "mutex"
#include "functional"
std::timed_mutex mutex;

int main() {

    std::function < void(int) > func1 = [](int k) {
        std::lock_guard<std::timed_mutex> lock(mutex);
        for (int i = 0; i < k; i++) {
            std::cout << i << " ";

        }
        lock.unlock();//报错,不支持中途解锁,会导致锁的粒度太大,可以使用unique_lock
        std::cout << std::endl;
    };
    return 0;
}

std::atomic相关
c++11提供了原子类型std::atomic,理论上这个T可以是任意类型

#include "iostream"
#include "thread"
#include "mutex"
#include "functional"
struct NewCounter { // 使用原子变量的计数器
    std::atomic<int> count;
    void add() {
        ++count;
        // count.store(++count);这种方式也可以
    }

    void sub() {
        --count;
        // count.store(--count);
    }

    int get() {
        return count.load();
    }
};
int main() {

  
    return 0;
}

std::call_once相关
c++11提供了std::call_once来保证某一函数在多线程环境中只调用一次,它需要配合std::once_flag使用

#include "iostream"
#include "thread"
#include "mutex"
#include "functional"

std::once_flag onceFlag;
int main() {

    std::function<void()> func1 = []() {
        std::call_once(onceFlag, []() {
            std::cout << "call once" << std::endl;
        });
    };
    std::thread threads[5];
    for (int i = 0; i < 5; ++i) {
        threads[i] = std::thread(func1);
    }
    for (auto &th: threads) {
        th.join();
    }
    return 0;
}

std::condition_variable相关
条件变量是c++11引入的一种同步机制,它可以阻塞一个线程或者个线程,直到有线程通知或者超时才会唤醒正在阻塞的线程,条件变量需要和锁配合使用,这里的锁就是上面介绍的std::unique_lock。

#include                 // std::cout
#include                 // std::thread
#include                 // std::mutex, std::unique_lock
#include     // std::condition_variable

std::mutex mtx; // 全局互斥锁.
std::condition_variable cv; // 全局条件变量.
bool ready = false; // 全局标志位.

void do_print_id(int id) {
    std::unique_lock<std::mutex> lck(mtx);
    cv.wait(lck, []{ return ready; });//一定要这么写,用while还是会出现虚假唤醒,不知道为啥
    // 线程被唤醒, 继续往下执行打印线程编号id.
    std::cout << "thread " << id << '\n';
}
void go() {
    std::unique_lock<std::mutex> lck(mtx);
    ready = true; // 设置全局标志位为 true.
    cv.notify_one(); // 唤醒所有线程.
}

void vecthread() {
    std::thread threads[10];
    // spawn 10 threads:
    for (int i = 0; i < 10; ++i)
        threads[i] = std::thread(do_print_id, i);
    for (auto &th: threads) th.detach();
}

int main() {
    std::thread create_vecthread(vecthread);
    create_vecthread.join(); //因为join了以后必定先执行
    std::cout << "10 threads ready to race...\n";
    go(); // go!
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return 0;
}

std::future相关
c++11关于异步操作提供了future相关的类,主要有std::future、std::promise和std::packaged_task,std::future比std::thread高级些,std::future作为异步结果的传输通道,通过get()可以很方便的获取线程函数的返回值,std::promise用来包装一个值,将数据和future绑定起来,而std::packaged_task则用来包装一个调用对象,将函数和future绑定起来,方便异步调用。而std::future是不可以复制的,如果需要复制放到容器中可以使用std::shared_future。

小知识
三者之间的关系
std::future用于访问异步操作的结果,而std::promise和std::packaged_task在future高一层,它们内部都有一个future,promise包装的是一个值,packaged_task包装的是一个函数,当需要获取线程中的某个值,可以使用std::promise,当需要获取线程函数返回值,可以使用std::packaged_task。

#include 
#include 
#include 
#include      // std::promise, std::future

void print_int(std::future<int>& fut) {
    int x = fut.get();                    // 获取共享状态的值.
    std::cout << "value: " << x << '\n';  // 打印 value: 10.
}

int main ()
{
    std::promise<int> prom;                    // 生成一个 std::promise 对象.
    std::future<int> fut = prom.get_future();  // 和 future 关联.
    std::thread t(print_int, std::ref(fut));   // 将 future 交给另外一个线程t.
    prom.set_value(10);                        // 设置共享状态的值, 此处和线程t保持同步.
    t.join();
    return 0;
}
#include 
#include 
#include 
#include 

using namespace std;

int func(int in) {
    return in + 1;
}

int main() {
    std::packaged_task<int(int)> task(func);
    std::future<int> fut = task.get_future();
    std::thread(std::move(task), 5).detach();
    cout << "result " << fut.get() << endl;
    return 0;
}

async相关
async是比future,packaged_task,promise更高级的东西,它是基于任务的异步操作,通过async可以直接创建异步的任务,返回的结果会保存在future中,不需要像packaged_task和promise那么麻烦,关于线程操作应该优先使用async
async具体语法如下:

async(std::launch::async | std::launch::deferred, func, args...);

功能:第二个参数接收一个可调用对象(仿函数、lambda表达式、类成员函数、普通函数…)作为参数,并且异步或是同步执行他们。

对于是异步执行还是同步执行,由第一个参数的执行策略决定:

(1)、std::launch::async 传递的可调用对象异步执行;

(2)、std::launch::deferred 传递的可调用对象同步执行;

(3)、std::launch::async | std::launch::deferred 可以异步或是同步,取决于操作系统,我们无法控制;

(4)、如果我们不指定策略,则相当于(3)。

对于执行结果:

我们可以使用get、wait、wait_for、wait_until等待执行结束,区别是get可以获得执行的结果。如果选择异步执行策略,调用get时,如果异步执行没有结束,get会阻塞当前调用线程,直到异步执行结束并获得结果,如果异步执行已经结束,不等待获取执行结果;如果选择同步执行策略,只有当调用get函数时,同步调用才真正执行,这也被称为函数调用被延迟。

#include 
#include 
#include 
#include 
#include "time.h"
#include "chrono"

using namespace std;

int func(int in) {
    this_thread::sleep_for(chrono::microseconds(1000));
    return in + 1;
}

int main() {
    std::future<int> res = std::async(func, 5);
    std::future_status status;
    do {
        status = res.wait_for(std::chrono::microseconds(0));
        switch (status)
        {
            case std::future_status::ready:
                std::cout << "Ready..." << std::endl;
                //获取结果
                std::cout << res.get() << std::endl;
                break;
            case std::future_status::timeout:
                std::cout << "timeout..." << std::endl;
                break;
            case std::future_status::deferred:
                std::cout << "deferred..." << std::endl;
                break;
            default:
                break;
        }

    } while (status != std::future_status::ready);
    cout << res.get() << endl; // 这里会报错,使用std::shared_future不会报错
    return 0;
}

std::future与std::shard_future的用途都是为了占位,但是两者有些许差别。std::future的get()成员函数是转移数据所有权;std::shared_future的get()成员函数是复制数据。 因此: future对象的get()只能调用一次;无法实现多个线程等待同一个异步线程,一旦其中一个线程获取了异步线程的返回值,其他线程就无法再次获取。 std::shared_future对象的get()可以调用多次;可以实现多个线程等待同一个异步线程,每个线程都可以获取异步线程的返回值。

智能指针

c++11引入了三种智能指针:

  • std::shared_ptr
  • std::weak_ptr
  • std::unique_ptr

shared_ptr
shared_ptr使用了引用计数,每一个shared_ptr的拷贝都指向相同的内存,每次拷贝都会触发引用计数+1,每次生命周期结束析构的时候引用计数-1,在最后一个shared_ptr析构的时候,内存才会释放。
智能指针还可以自定义删除器,在引用计数为0的时候自动调用删除器来释放对象的内存,代码如下:

std::shared_ptr<int> ptr(new int, [](int *p){ delete p; });

weak_ptr
weak_ptr是用来监视shared_ptr的生命周期,它不管理shared_ptr内部的指针,它的拷贝的析构都不会影响引用计数,纯粹是作为一个旁观者监视shared_ptr中管理的资源是否存在,可以用来返回this指针和解决循环引用问题。
unique_ptr
std::unique_ptr是一个独占型的智能指针,它不允许其它智能指针共享其内部指针,也不允许unique_ptr的拷贝和赋值。使用方法和shared_ptr类似,区别是不可以拷贝,unique_ptr也可以像shared_ptr一样自定义删除器,使用方法和shared_ptr相同。

#include 
#include 
#include "chrono"

using namespace std;

class A {
public:
    A() {};

    ~A() {
        std::cout << "111" << std::endl;
    }
};
int main() {
    std::shared_ptr<A> sharedPtr = std::make_shared<A>();
    std::unique_ptr<A> uniquePtr = std::unique_ptr<A>(new A());
    std::shared_ptr<A> sharedPtr1(new A(), [](A *a) {
        std::cout << "wo" << std::endl;
    });//自定义删除器
    //输出wo 111 111 后构造的先析构
    return 0;
}

基于范围的for循环

vector<int> vec;

for (auto iter = vec.begin(); iter != vec.end(); iter++) { // before c++11
    cout << *iter << endl;
}

for (int i : vec) { // c++11基于范围的for循环
    cout << "i" << endl;
}

委托构造函数

委托构造函数允许在同一个类中一个构造函数调用另外一个构造函数,可以在变量初始化时简化操作,通过代码来感受下委托构造函数的妙处吧:

struct A {
    A(){}
    A(int a) { a_ = a; }

    A(int a, int b) : A(a) { b_ = b; }

    A(int a, int b, int c) : A(a, b) { c_ = c; }

    int a_;
    int b_;
    int c_;
};

继承构造函数

继承构造函数可以让派生类直接使用基类的构造函数,如果有一个派生类,我们希望派生类采用和基类一样的构造方式,可以直接使用基类的构造函数,而不是再重新写一遍构造函数,老规矩,看代码:

不使用继承构造函数:

struct Base {
    Base() {}
    Base(int a) { a_ = a; }

    Base(int a, int b) : Base(a) { b_ = b; }

    Base(int a, int b, int c) : Base(a, b) { c_ = c; }

    int a_;
    int b_;
    int c_;
};

struct Derived : Base {
    Derived() {}
    Derived(int a) : Base(a) {} // 好麻烦
    Derived(int a, int b) : Base(a, b) {} // 好麻烦
    Derived(int a, int b, int c) : Base(a, b, c) {} // 好麻烦
};
int main() {
    Derived a(1, 2, 3);
    return 0;
}

使用继承构造函数:

struct Base {
    Base() {}
    Base(int a) { a_ = a; }

    Base(int a, int b) : Base(a) { b_ = b; }

    Base(int a, int b, int c) : Base(a, b) { c_ = c; }

    int a_;
    int b_;
    int c_;
};

struct Derived : Base {
    using Base::Base;
};

int main() {
    Derived a(1, 2, 3);
    return 0;
}

只需要使用using Base::Base继承构造函数,就免去了很多重写代码的麻烦。

nullptr

nullptr是c++11用来表示空指针新引入的常量值,在c++中如果表示空指针语义时建议使用nullptr而不要使用NULL,因为NULL本质上是个int型的0,其实不是个指针。举例:

void func(void *ptr) {
    cout << "func ptr" << endl;
}

void func(int i) {
    cout << "func i" << endl;
}

int main() {
    func(NULL); // 编译失败,会产生二义性
    func(nullptr); // 输出func ptr
    return 0;
}

final

final用于修饰一个类,表示禁止该类进一步派生和虚函数的进一步重载。

struct Base final {
    virtual void func() {
        cout << "base" << endl;
    }
};

struct Derived : public Base{ // 编译失败,final修饰的类不可以被继承
    void func() override {
        cout << "derived" << endl;
    }

};

override

override用于修饰派生类中的成员函数,标明该函数重写了基类函数,如果一个函数声明了override但父类却没有这个虚函数,编译报错,使用override关键字可以避免开发者在重写基类函数时无意产生的错误。

struct Base {
    virtual void func() {
        cout << "base" << endl;
    }
};

struct Derived : public Base{
    void func() override { // 确保func被重写
        cout << "derived" << endl;
    }

    void fu() override { // error,基类没有fu(),不可以被重写
        
    }
};

default

c++11引入default特性,多数时候用于声明构造函数为默认构造函数,如果类中有了自定义的构造函数,编译器就不会隐式生成默认构造函数,如下代码:

struct A {
    int a;
    A(int i) { a = i; }
};

int main() {
    A a; // 编译出错
    return 0;
}

上面代码编译出错,因为没有匹配的构造函数,因为编译器没有生成默认构造函数,而通过default,程序员只需在函数声明后加上“=default;”,就可将该函数声明为 defaulted 函数,编译器将为显式声明的 defaulted 函数自动生成函数体,如下:

struct A {
    A() = default;
    int a;
    A(int i) { a = i; }
};

int main() {
    A a;
    return 0;
}

编译通过。

delete

c++中,如果开发人员没有定义特殊成员函数,那么编译器在需要特殊成员函数时候会隐式自动生成一个默认的特殊成员函数,例如拷贝构造函数或者拷贝赋值操作符,如下代码:

struct A {
    A() = default;
    int a;
    A(int i) { a = i; }
};

int main() {
    A a1;
    A a2 = a1;  // 正确,调用编译器隐式生成的默认拷贝构造函数
    A a3;
    a3 = a1;  // 正确,调用编译器隐式生成的默认拷贝赋值操作符
}

而我们有时候想禁止对象的拷贝与赋值,可以使用delete修饰,如下:
struct A {
A() = default;
A(const A&) = delete;
A& operator=(const A&) = delete;
int a;
A(int i) { a = i; }
};

int main() {
A a1;
A a2 = a1; // 错误,拷贝构造函数被禁用
A a3;
a3 = a1; // 错误,拷贝赋值操作符被禁用
}
delele函数在c++11中很常用,std::unique_ptr就是通过delete修饰来禁止对象的拷贝的。

explicit

explicit专用于修饰构造函数,表示只能显式构造,不可以被隐式转换,根据代码看explicit的作用:

#include 
using namespace std;

class Point {
public:
    int x, y;
    Point(int x = 0, int y = 0)
            : x(x), y(y) {}
};

void displayPoint(const Point& p)
{
    cout << "(" << p.x << ","
         << p.y << ")" << endl;
}

int main()
{
    displayPoint(1);//输出(1,0),y使用了默认值0
    Point p = 1;
}

我们定义了一个再简单不过的Point类, 它的构造函数使用了默认参数. 这时主函数里的两句话都会触发该构造函数的隐式调用. (如果构造函数不使用默认参数, 会在编译时报错)

显然, 函数displayPoint需要的是Point类型的参数, 而我们传入的是一个int, 这个程序却能成功运行, 就是因为这隐式调用. 另外说一句, 在对象刚刚定义时, 即使你使用的是赋值操作符=, 也是会调用构造函数, 而不是重载的operator=运算符.

这样悄悄发生的事情, 有时可以带来便利, 而有时却会带来意想不到的后果. explicit关键字用来避免这样的情况发生。

// 加了explicit之后的代码
#include 
using namespace std;

class Point {
public:
    int x, y;
    explicit Point(int x = 0, int y = 0)
            : x(x), y(y) {}
};

void displayPoint(const Point& p)
{
    cout << "(" << p.x << ","
         << p.y << ")" << endl;
}

int main()
{
    displayPoint(Point(1));
    Point p(1);
}

const

const字面意思为只读,可用于定义变量,表示变量是只读的,不可以更改,如果更改,编译期间就会报错。
主要用法如下:

  • 用于定义常量,const的修饰的变量不可更改。
const int value = 5;
  • 指针也可以使用const,这里有个小技巧,从右向左读,即可知道const究竟修饰的是指针还是指针所指向的内容。“左定值,右定向,const修饰不变量”。
char *const ptr; // 指针本身是常量
const char* ptr; // 指针指向的变量为常量
  • 在函数参数中使用const,一般会传递类对象时会传递一个const的引用或者指针,这样可以避免对象的拷贝,也可以防止对象被修改。
class A{};
void func(const A& a);
  • const修饰类的成员变量,表示是成员常量,不能被修改,可以在初始化列表中被赋值。
class A {
    const int value = 5;
};
class B {
    const int value;
    B(int v) : value(v){}
};
  • 修饰类成员函数,表示在该函数内不可以修改该类的成员变量。
class A{
    void func() const;
};
  • 修饰类对象,类对象只能调用该对象的const成员函数。
class A {
    void func() const;
};
const A a;
a.func();
  • const参数传递和函数返回值
  • const参数传递和函数返回值

constexpr

enum class

非受限联合体

sizeof

自定义字面量

内存对齐

thread_local

基础数值类型

随机数功能

正则表达式

chrono

duration

time_point

clocks

steady_clock

system_clock

新增数据结构

新增算法


三、C++14新特性


‍四、C++17新特性


五、C++20新特性


参考文献

  1. https://zhuanlan.zhihu.com/p/139515439

文章总结

提示:该文章还在更新中:

  不要走开!作者还在持续更新中,建议点赞关注加收藏!

https://zhuanlan.zhihu.com/p/139515439






更多好文推荐

2021-4月Python 机器学习——中文新闻文本标题分类
2021年4月-(计算机网络)小型校园网络模拟搭建,最全最准确版
2022-10-31-基于用户的协同过滤推荐算法实现+MAE+RMSE
2022-11-28-大数据可视化,特征维度大于50
2023-3-9-一篇简短的文章把C++左右值关系讲的透透彻彻

上一篇
End
下一篇

你可能感兴趣的:(汤姆C++系列,c++,学习,开发语言)