zoukankan      html  css  js  c++  java
  • 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也一样


  • 相关阅读:
    macbook 无声音解决方案
    webapck dev server代理请求 json截断问题
    百度卫星地图开启
    服务器 nginx配置 防止其他域名绑定自己的服务器
    记一次nginx php配置的心路历程
    遇到npm报错read ECONNRESET怎么办
    运行svn tortoiseSvn cleanup 命令失败的解决办法
    svn add 命令 递归目录下所有文件
    m4出现Please port gnulib freadahead.c to your platform! Look at the definition of fflush, fread, ungetc on your system, then report this to bug-gnulib."
    Ubuntu下安装GCC,mpc、mpfr、gmp
  • 原文地址:https://www.cnblogs.com/leehm/p/13296281.html
Copyright © 2011-2022 走看看