• C++11 新特性


    1. 新增扩展int类型:long long int,也称long long。 

      在C++11新特性中,long long一定是最容易被接受的一个。多数程序员看到它时甚至不会意识到这是一个新特性。

      与 long long 整型相关的一共有3个:LONG_MIN、LONG_MAX 和ULONG_MAX, 它们分别代表了平台上最小的long long 值、最大的long long 值,以及最大的unsigned long long 值。long long int  64位 , unsigned long long  64位

      int64_t用来表示64位整数,在32位系统中是long long int,在64位系统中是long int,所以打印int64_t的格式化方法是:

    printf("%ld", value);      // 64bit OS  
    printf("%lld", value);     // 32bit OS  

    2.   noexcept

      noexcept替代传统的throw抛异常,更大的作用就是保证应用程序的安全

      比如: 一个类析构函数不应该抛异常,那么对于常被析构调用的delete,c++11默认将delete设置为noexcept,

           使用: .h.cpp文件都需要写

      自定义的函数可以如下:
      void func() noexcept;
      void func() noexcept(true/false);

    3.   允许使用 = 或者 {} 进行就地初始化非静态数据成员 

    如果同时使用类的初始化列表(又称:冒号语法初始化),则初始化列表生效

    如下,初始化后,m_high=10,m_strName=test
    看起来,是就地初始化先执行,然后才是初始化列表。

    class Father
    {
    public:
        Father()
        :m_high(10)
        ,m_strName("test")
        {}
    privateint m_high = 20;
        std::string m_strName{"name1"};

    注意:
    c++11中使用{}初始化,是唯一一种可以防止类型收窄,narrowing的初始化方式, 即:数据变化或者精度丢失,如float->int,高精度到低精度等
    这也是{}与其它方式初始化不一样的地方
    使用{}初始化,编译器会检查是否出现类型收窄。因此,建议能用{}初始化变量

    4. final/override

    final:       使派生类无法覆盖它所修饰的虚函数
    override: 使用override,则派生类必须覆盖即重写所修饰的虚函数,建议能用就用,避免手一抖写错,新增了一个函数而无法识别

    5.  using

    在c++11前,如果派生类要使用基类的成员函数,可以通过声明using来完成

    c++11中,此想法被扩展到构造函数中,使用using来声明继承基类的构造函数,但只能初始化基类的成员,如果要初始化派生类自己的成员,可以使用就地初始化。

    如:
    #include <iostream>
    #include <string>
    
    class A
    {
    public:
        A(int i);
        A(int i, char c);
    
    public:
        double f(double i) {
            std::cout << __FUNCTION__ << std::endl;
            return i;
        }
    
    private:
        int m_num = 0;
        char m_c = 'A';
    };
    
    
    class B : public A
    {
    public:
        using A :: A;  //使用A的构造函数,C++11后支持
    public:
        using A::f;    //使用A的f函数,c++11前就支持
        int f(std::string name) {
            std::cout << __FUNCTION__ << std::endl;
            return 0;
        }
    
    private:
        std::string m_strName{"testB"};
    };       
    
    
    使用:
    int main()
    {
        int i = 10;
        char c = 'B';
        double d = 9.98f;
        
        B b{i,c};   //c++11中,使用using A :: A 表示使用基类A的构造,
                    //如果没有使用using,此处B没有构造函数,编译报错,如果要实现与A一样的构造,需要全部重写类似B的构造,比较繁琐
                    //如:B(int i); B(int i, char c).
                    //有了using,直接using,然后自己的变量,就地初始化,方便。
        
        b.f(d);    //使用using A::f; 表示使用基类的A的f(double)函数
                   //如果没有,此处基类A中的f(double)已经被B的f(string)的覆盖了,编译会报错。
    
    }

    6. 委派构造

      看名字,就知道与构造函数有关
      c++11中,所谓委派构造是指将构造的任务委派给了目标函数来完成类的构造
      直白点,就是构造函数委派另外的构造函数进行构造
      注意: 当使用了委派构造后,就不能使用初始化列表进行构造了。只能选其一

    class Info
    {
    public:
        Info():m_a(0),m_strName("") { 
            Init();
        }
        Info(int i):Info()       //委派info构造
    { Init(); m_a
    = i; } Info(std::string name,int i) :Info(i) //委派info(i)构造
      {
            Init();
            m_strName = name;
        }
        //error, if use initialize list, then can not use delegate constructor func
    //     Info(std::string name) :Info(), m_a(i) {
    //         Init();
    //         m_strName = name;
    //     }
    
    private:
        void Init()
        {
            //do any other initialize
            std::cout << __FUNCTION__ << __FILE__ << __LINE__ << std::endl;
            
        }
    
    private:
        int m_a;
        std::string m_strName;
    
    };

    7.左值,右值

    c语言一个典型的说法,=左边的是是左值,右边的是右值
    c++中,一个更广泛认同的说法:那就是可以取地址的,有名字的就是左值,反之就是右值
    更为细致的,c++中,右值分为:将亡值和纯右值

    纯右值:

    1. 函数的非引用返回的临时变量,
    2. 一些计算表达式值,如2+3,
    3. 不跟对象关联的纯值,如:1,c,d,
    4. 类型转换的返回值
    5. lambda表达式


    将亡值:则是与右值引用 &&相关的
    1. 返回右值引用的函数返回值
    2. std::move
    3. 类型转发后的&&

    左值引用 &
    右值引用 &&

    c++中,由于右值通常不具有名字,所以我只能通过引用的方式找打它,因此就有了右值引用的表达:&&

    比如: T&& a = returnValue();

    returnValue返回临时对象,本该在函数返回后,生命周期结束,但是由于使用右值引用T&& a接收,因此其生命就被绑定到a上面,
    相比于: T a = returnValue();
    减少了一次对象的析构与一次对象的构建。

    C++11之后引入了引用折叠规则,如:T &&,
    当实参类型是一个左值引用,则会推导为X& &&,引用折叠规则最终为X&
    当实参类型是一个右值引用,则会推导为X&& &&,引用折叠规则最终为X&&。

    即,可以理解T&&为万能引用,无论左值引用右值引用,它都可以接收。

    8 . 移动构造std::move

    std::move 其实就是将左值强制转换为右值引用,继而我们可以继续通过右值引用使用这个值。而被转换的左值,其声明周期也没有因为转换而变化
    static_cast<T&&>(lValue)

    使用场景:
      假如是个左值,如成员变量为指针或者引用,没有使用右值,通过左值构造对象或者传递参数,就会进行默认的拷贝构造,多一次析构与创建
      但是如果使用std::move转换为右值,进入移动构造,则会减少了一次对象的析构与一次对象的构建

      如下: 使用move转为右值,强行转换进行移动构造

    #pragma once
    #include <utility>
    
    class HugeMem
    {
    public:
        HugeMem(int size) :sz(size > 0 ? size:1) {
            c = new char[sz];
        }
        //移动构造
        HugeMem(HugeMem && hm) :sz(hm.sz), c(hm.c) {
            hm.c = nullptr;
        }
        virtual ~HugeMem() { 
            delete [] c; 
            c = nullptr; 
        }
    private:
        char *c;
        int sz;
    };
    
    class Moveable
    {
    public:
        Moveable() :c(new char[3]),h(1024) {
        }
        //强制使用移动构造,如果没有,会进入拷贝构造
        Moveable(Moveable && hm) : c(hm.c),h(std::move(hm.h)) {
            hm.c = nullptr;
        }
        virtual ~Moveable() {
            delete[] c;
            c = nullptr;
        }
    private:
        char *c;
        HugeMem h;
    };

    实际上: 程序员在编写移动构造函数的时候,应该总记得使用std::move将类似于指针,文件句柄等资源转为右值进行传参
    这样的好处:

      1. 如果成员支持移动构造,直接移动构造
      2. 如果成员不支持移动构造,那么将接收的还是左值,实现拷贝构造,也不会引起什么大问题。

    9. std::forward

    背景:
      void forwardTest(T t){ run(t);}

    如上: 实际执行的是run,forwardTest只是对外包装一层,转发了调用而已。
    似乎平常,都是这么写代码的,貌似是透传,但实际却没那么简单,

    这中间在参数t传递给run的时候,就进行了一次临时对象的拷贝,尽管功能上实现了转发,但谈不上完美。

    所以,通常需要做的是引用类型,引用不会有拷贝的开销,但如果碰上使用右值的地方,就没法使用了,

    或者,run接收的是const t,那岂不是要为run重载几个版本?,
    如此std::forward登场了
    void forwardTest(T&& t){ run(std::forward<t>);}

    结合折叠规则和自动推导
    如果是左值, std::forward转化为static_cast<T&>(t)
    如果是右值, std::forward转化为static_cast<T&&>(t)

    总结:
      move和forward在实现上差别并不大,都是强转,不过库既然这么设计,也许是为了让不同的名字有对应的用途,以对应将来的扩展
      区别就是:move强行转换成了右值,而forward则是保留了原有的左右值,

    使用场景:
      但需要将一参数直接透传到另一个函数执行时,可以使用std::forward,减少一次对象的拷贝与析构

    10. 显示类型转换 explict

    c++11之前,explict用来修饰构造函数,显示指定构造函数类型,不能通过隐式转换

    class A
    {
    publi:
    A(int i):m_data(i){}
    private:
    int m_data;
    }

    没有使用explict,可以有如:A a = 10;隐式转换调用了构造函数。
    加入了explic后,就会提示需要显示指定,编译通不过,只能A a(10);

    c++11 以后,允许explict用来修饰类型转换操作符()上,意味着,只有通过直接构造或者强制类型转换才成功使用类型.

    class B;
    class A
    {
    publi:
    explict operator B () const {return B();}
    }
    
    void Func(B b);
    void main()
    {
    A a;
    B b1(a); //直接构造初始化
    B b2 = static_cast<B>(a); //强制类型转换
    //其它都不行,拷贝构造
    B b3 = a; 
    Func(a); 
    }

    11. POD

    12. 非首先联合体union
    c++11之前,联合体成员数据类型有一些限制,比如:自定义的结构体类型若增加了构造函数,则是不允许作为联合体的成员的,
    c++11之后,取消了对联合体成员数据类型的限制。标准规定,任何非引用类型都可以作为联合体的成员
    需要自己写构造函数初始化一些带有构造函数的类成员。(默认的构造函数会被删掉)

    13. inline namespace, 解决父子命名空间的繁锁

    14. 使用using定义类型的别名,类似typdef   

      typedef unsigned int UINT   

      using UINT = unsigned int

    15. 右 > 的改进

      c++11之前,实例化模板类遇到两个>中间需要加空格,避免编译错误,因为会被当做右移>>

      如:vector<vector<int> > vec
      c++11后,没有了,不会报错,自动匹配解析了。
      为了避免与右移动重复,真正右移动的时候,建议(不是强制)加括号避免被解析出错如:(3>>2)


    16. typeid

      c++11之前,就支持RTTI运行时类型识别,RTTI为每个类型生成type_info,typeid(类型)可以返回变量的类型type_info信息数据
      而type_info.name()成员函数就可以返回类型的名字。

      C++11之后,新增了hash_code()这个成员函数,返回类型的唯一hash,用于类型的比较=====C#有点像

    17. auto与decltype

      c++11新增的自动类型推导

      auto从变量推导:
        如:auto i=10;
      不能使用auto四种情况:
        1. func函数,auto不能作为形参
        2. 结构体,非静态成员变量,不能为auto
        3. 声明auto数组
        4. 实例化模板的时候,不能使用auto,如std::vector<auto> v = {1};

      decltype从表达式推导
        如:auto a = 10, b = 20;
        decltype(a + b) c = 10; c的类型与a+b一样

      decltype规则:decltype(e)
        1. 如果e是不带括号的标记符表达式或者类成员访问表达式,那么decltype(e)就是e所命名的实体类型, e不能是被重载的函数
        2. 假设e的类型是T,如果e是将亡值,则decltype(e) = T&&
        3. 假设e的类型是T,如果e是左值,则decltype(e) = T&
        4. 假设e的类型是T,则decltype(e) = T

        最容易迷糊的是1和3,来我们看:
        int i = 0;
        decltype(i) a; ===规则1, i不带括号,就是i,int
        decltype( (i) ) a ===规则3, i带括号,(i)不是一个表达式,确是一个左值可以取地址,因此是 int&

        解释一下1中的标记符表达式:
          基本上除去所有关键字,字面量等编译器标记的之外,所有自己定义的变量都是,成员变量也是
          如: int arry[10];
            int *prt = nullptr;
            Struct S {double d;}s;
            像array,prt,s.d都是,而类似a+b这种则不是,得归到规则4种。

        decltype与auto不同的是:
          auto不能带带走cv修饰符即:const/volatile
          decltype是能带走的,即没法继承cv修饰符,被去掉了。

    18. 追踪返回类型:自动推导返回值

      解决泛型种如下问题:比如需要泛型返回值,自动推导,下面会编译不过,因为不认识t1和t2

    template<typename T1, typename T2>
      decltype(t1 + t2) Sum(T1 t1, T2 t2){ 
        return t1 + t2; 
      }
      //c++11之后:返回值后置
      auto Sum(T1 t1, T2 t2) -> decltype(t1 + t2) 
      { 
        return t1 + t2; 
      }
    如:
    old:
        int func(char *a, int b);
    new:
        auto func(char *a, int b)->int;

    19. for循环

      int array = {123};
      for(auto i : array)
      {}

    20. enum

      c++11以前: enum的变量全局的,容易混,容易污染
        如: enum Type{General, Light, Medium, Heavy}
           enum Category {General, Pisotol, MachineGun, Cannon }
      General有重复,需要自己加命名空间,加类封装等等

     c++11 强类型枚举: enum class type name{。。。}
      如: enum class char C {c1 = 1, c2= 2}
         enum class int D {D1 = 1, D2 = 2, Dbig = 0xFFFF}
      使用: C:c1 D:D1
      加上了名称,强作用域,隔开,type可以为wchar_t的任意类型。


    21. 智能指针

      1. unique_ptr 看名字,独占的指针, 从实现上看,是一个删除了拷贝构造,保留了移动构造的指针封装
        std::unique_ptr<int> p1(new int(11));
        std::unique_ptr<int> p2 = p1;   //编译不过,独占,不能被复制
        std::unique_ptr<int> p3 = std::move(p1); //唯一,可以移动构造,完了之后,p1失效


      2. share_prt 看名字,共享指针,有引用计数,只要有赋值,就是++,到0后自动删除


      3. weak_ptr 不会增加引用技术,通过lock返回share_ptr,如果无效,返回空,交叉引用使用,父->子,share,子->父 weak, 避免相互,无法释放,内存泄漏。
        share_ptr<int> sp(new int(11)); //计数1
        weak_ptr<int> wp = sp// 计数1,不增加
        share_ptr<int> sp1 = wp.lock() //转化为share,如果sp已经被删除,sp1为空


    22. constexptr

      由constexptr修饰的变量就是所谓的常量表达式值。

      c++11中, constexptr是不能修饰自定义的变量的。

      const int i = 10; //常量表达式
      constexptr int j = i; //常量表达式值
      二者大部分没啥区别,有一点:
        如果i在全局声明,则编译器一定会为i产生数据
        而对于j,只要没有代码显示使用j的地址,编译器可以不为其产生数据,而仅仅作为编译数据


    23. 变长模板,变长函数/参数


    24. 多线程

      std::thread, lock_guard, mutex, condition_variable

      

      std::lock_guard
      std::unique_lock

        1. 正常情况,为了省去手动的lock/unlock,采用lock_guard包装,即可打到加锁保护的目的
          std::lock_guard<std::mutex> lk(mQueMutex);

        2. 线程在wait的时候,就得使用unique_lock,不能使用lock_guard
          std::unique_lock<std::mutex> lk(mQueMutex);
          mQueCondVar.wait_for(lk, std::chrono::milliseconds(10));
          理由:
            std:lock_guard中无法暂时释放锁和加锁,而unique_lock可以临时释放锁,枷锁
            wait过程需要临时释放锁,如果一直锁着不释放,会永远无法捕捉变量得更新

      std::recursive_mutex
      std::mutex

        1. 正常情况,mutex配合上述lock,直接使用

        2. 同一个线程,重复加锁,使用mutex则会导致死锁,此时就需要使用std::recursive_mutex

        recursive_mutex 递归锁
          可以允许一个线程对同一互斥量多次加锁,解锁时,需要调用与lock()相同次数的unlock()才能释放使用权
          如下也可行:std::lock_guard<std::recursive_mutex>

      原子类型: atomic_bool, atomic_int,......

      内存顺序: memory_order_relaxed...

        c++11中,所有的原子操作都可以使用memory_order作为一个参数
        int t = 1;
        atomic<int> a;
        a.store(1, memory_order_relaxed);

    25. 线程局部存储

      ====c++11只做了语法统一,没有性能的规定
      int thread_local errorCode;
      一旦声明thread_local类型变量,其值从线程开始初始化,结束后不在有效。

      如:两个线程T1,T2。

      1. 每个定义一个全局errorCode,各自为战
      2. 定义一个全局的errorCode,到底是哪个报的error,无法确定

    26. nullptr

      从0到NULL, 再从NULL到nullptr

    27. 类的默认函数: 五大函数+析构

      1. 构造
      2. 拷贝构造
      3. 移动构造
      4. 拷贝赋值(operator =)
      5. 移动赋值
      6. 析构

    28. ==default和==delete


    29. lambda表达式


    30. std::function和std::bind


    31. 数据对齐:

      操作符: alignof() --->查看对齐字节数

    struct A{
    int a;
    char c;
    };
    //alignof(A) = 8 说明8字节对齐

      对齐描述符: alignas() ---->指定使用几个字节对齐,既可以是类型,也可以是具体数值

    struct alignas(4) A{
    int a;
    char c;
    };
    //使A按4字节对齐
    //alignas(double)和alignas(8)一样,
    //stl:std::align等


    32. unicode支持,常见的由UTF-8, UTF-16,UTF-32

      Windows UTF-16
      linux/mac UTF-8

      UTF-8:变长编码unicode,英文通常1字节表示,与ASCII码兼容,

          中文采用3字节,省空间,一个汉字就是 3+‘’= 10字节, 没有大小端问题。 没有u8string,需要函数与多字节转换

      UTF-16:定长编码, 由字节序问题,LE和BE版本 有u16string,u32string,方便操作


      c++11前: wchar_t表示,Windows下被实现位16位宽,理论长度可以为8位,16位,32位,
      这样各个平台不一致,难以移植


      c++11后:     char16_t        16字节, 存储UTF-16编码的unicode数据
            char32_t         32字节, UTF-32
            char                8字节,   UTF-8,
      各个平台统一。
      增加前缀来表示unicode字符
        u8    UTF-8       u8"123"
        u      UTF-16     u“ab”
        U     UTF-32     U"唐"

      之前的 L 表示宽字符wchar_t,四种前缀
      L“123456”

      普通的就不用加,默认的 “123456”

    33. 原生字符串的支持 R

      所见即所得,不转义,看见的就是输入的
      cout << R("hello world") << endl;
      输出:

      hello world   

            没有转义,是什么就输出什么。

      unicode u8R, u16R, u32R也一样


  • 相关阅读:
    OpenCASCADE Chamfer 3D Basics
    OpenCASCADE Chamfer 2D
    .NetCore 连接 Oracle 数据库,直接C# 或者 ORM框架(EFCore、XPO)
    心内科疾病指南
    HttpClient 调用 RestAPI 接口的用法
    在 Blazor 应用中使用 DevExtreme widgets
    2021 最近一次检查甘油三脂,验证苯扎贝特的效果。
    紫鹊界本味湘菜,
    如何Rest接口获取网上的股票数据,有哪些资源?-- 推荐Tushare金融数据
    优秀常用的「资源搜索网站」,收藏
  • 原文地址:https://www.cnblogs.com/leehm/p/13296281.html
Copyright © 2020-2023  润新知