• C++11,17新特性


    参考学习自Effective Modern C++

    {},别名using,nullptr,新for循环,智能指针,移动语义,强枚举类型,右值引用,default与delete

    创建对象时注意区分()和{}

    • 大括号初始化可应用语境最宽泛

      有三种指定初始化值的方式

      int x(0);	//方式1,不能为非静态成员指定默认初始化值
      int y=0;	//方式2,不能为不可复制对象初始化,如std:atomic
      int z{0};	//方式3,等价于int z={0}; 
      
      • C++11引入统一初始化,即可用于一切场合的初始化,基础为大括号
      • 大括号可以指定容器初始内容,可以进行上述方式12不能执行的初始化
      • 大括号初始化新特性:禁止内建型别之间进行隐式窄化型别转换,而方式12则不会检查,并可能直接截断初始化值
    • C++有最令人苦恼的解析语法:任何能够解析为声明的都要解析为声明,大括号可以避免

      class Student
      {
      	Student(const char*name):student_name(name){}
      	const char*student_name;
      }
      //main函数里
      Student s1("小明"); //报错,请输入正确的类型
      //此处编译器会将该语句视为函数声明
      Student s1{"小明"}; //正确,函数声明不能用大括号指定形参列表
      
    • C++在构造函数重载决议期间,只要有任何可能(可隐式转换),大括号初始化物就会与带有std::initializer_list型别的形参相匹配,即使其他重载版本有更匹配的形参表

      std::initializer_list<T> 类型对象是一个访问 const T 类型对象数组的轻量代理对象,相当于存放常量值的vector

      作用是方便了对于STL的container的初始化,所以container一般有该类型参数的构造函数

      • C++规定,当使用一对空大括号来构造对象,对象既支持默认构造函数,又支持带有std::initializer_list型别形参的构造函数时,会执行默认构造函数

        • 若执意要调用一个带有std::initializer_list型别形参的构造函数,并传入一个空的std::initializer_list可采用以下两种

        • Student s1({});
          Student s2{{}};
          
    • 容器初始化结果不同

      • std::vector<int> v1(10,20); //含有10个元素,所以元素值为20
        std::vector<int> v1{10,20};	//含有2个元素,分别为10和20
        

    nullptr

    • nullptr 本身是指针类型,不能转化为整数类型,可以转换成任意其他指针类型
    • NULL:预处理变量,是一个宏,它的值是 0,定义在头文件 中,即 #define NULL 0
    • 因为 NULL 本质上是 0,在函数调用过程中,若出现函数重载并且传递的实参是 NULL,可能会出现,不知和哪一个函数匹配的情况;但是传递实参 nullptr 就不会出现这种情况。

    新for循环

    int main() {
        char arc[] = "abcde";
        vector<char>myvector(arc, arc + 5);
        //for循环遍历并修改容器中各个字符的值
        for (auto &ch : myvector) {
            ch++;
        }
        //for循环遍历输出容器中各个字符
        for (auto ch : myvector) {
            cout << ch;
        }
        return 0;
    }
    

    新语法格式的 for 循环遍历某个序列时,如果需要遍历的同时修改序列中元素的值,实现方案是在 declaration 参数处定义引用形式的变量

    Lambda

    Lambda 表达式,实际上就是提供了一个类似匿名函数的特性, 而匿名函数则是在需要一个函数,但是又不想费力去命名一个函数的情况下去使用的,一般常见于自定义函数(sort里的比较函数),匿名委托

    基本语法如下

    [捕获列表](参数列表) mutable(可选) 异常属性 -> 返回类型 {
    // 函数体
    }
    

    lambda 表达式内部函数体在默认情况下是不能够使用函数体外部的变量的, 这时候捕获列表可以起到传递外部数据的作用。

    auto不能用于模板参数表,但可以用于Lambda表达式的参数表中

    • 左值捕获
      • 值捕获:[name]
        • 前提是变量可以拷贝,与参数传值类似
        • 在函数体内对捕获的变量进行改变,实际是改变拷贝后的变量,外部变量不会受影响
        • 若想修改外部变量可以通过mutable
      • 引用捕获:[&name]
        • 与引用传参类似
      • 隐式捕获
        • [] 空捕获列表
        • [name1, name2, ...] 捕获一系列变量
        • [&] 引用捕获, 让编译器自行推导捕获列表
        • [=] 值捕获, 让编译器执行推导引用列表
    • 右值捕获
      • 表达式捕获
        • C++14 允许捕获的成员用任意的表达式进行初始化,即允许了右值的捕获, 被声明的捕获变量类型会根据表达式进行判断,本质同auto

    智能指针

    智能指针将基本类型指针封装为类对象指针,并在析构函数里编写delete语句删除指针指向的内存空间。

    所有的智能指针类都有一个explicit构造函数,以指针作为参数。因此不能自动将指针转换为智能指针对象,必须显式调用

    auto_ptr

    • 在 auto_ptr 对象销毁时,他所管理的对象也会自动被 delete 掉。

    • auto_ptr 采用 copy 语义来转移指针资源,转移指针资源的所有权的同时将原指针置为 NULL,拷贝后

      原对象变得无效,再次访问原对象时会导致程序崩溃。

    unique_ptr

    由 C++11 引入,旨在替代不安全的 auto_ptr。

    unique_ptr 则禁止了拷贝语义,但提供了移动语义,即可以使用std::move() 进行控制权限的转移

    它持有对对象的独有权——两个 unique_ptr 不能指向一个对象,即 unique_ptr 不共享它所管理的对象。
    内存资源所有权可以转移到另一个 unique_ptr,并且原始 unique_ptr 不再拥有此资源。

    shared_ptr

    shared_ptr第二个参数支持自定义释放规则,也可以借助lambda

    //指定 default_delete 作为释放规则
    std::shared_ptr<int> p6(new int[10], std::default_delete<int[]>());
    
    //自定义释放规则
    void deleteInt(int*p) {
        delete []p;
    }
    //初始化智能指针,并自定义释放规则
    std::shared_ptr<int> p7(new int[10], deleteInt);
    

    shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针,当然这需要额外的开销:

    1. shared_ptr 对象除了包括一个所拥有对象的指针外,还必须包括一个引用计数代理对象的指针;
    2. 时间上的开销主要在初始化和拷贝操作上, * 和 -> 操作符重载的开销跟 auto_ptr 是一样;

    环形引用:智能指针互相指向了对方,导致自己的引用计数一直为1,所以没有进行析构,这就造成了内存泄漏。

    weak_ptr

    weak_ptr 只对 shared_ptr 进行引用,而不改变其引用计数

    • 当被观察的 shared_ptr 失效后,相应的 weak_ptr 也相应失效,其lock方法将返回空。
    • weak_ptr并没有重载operator->和operator *操作符,因此不可直接通过weak_ptr使用对象。
    • 可解决环形引用问题,让其中一方使用weak_ptr

    强枚举类型

    传统枚举

    枚举值对应整型数值,默认从 0 开始

    1. 同作用域同名枚举值会报重定义错误。传统 C++ 中枚举常量被暴漏在同一层作用域中
    enum Fruits{Apple,Tomato,Orange};
    enum Vegetables{Cucumber,Tomato,Pepper};	//编译报Tomato重定义错误
    
    1. 枚举常量占用存储空间以及符号性不确定。C++ 标准规定 C++ 枚举所基于的“基础类型”是由编译器来具体实现
    enum A{A1=1,A2=2,ABig=0xFFFFFFFFU};
    enum B{B1=1,B2=2,BBig=0xFFFFFFFFFUL};
    
    int main()
    {
    	cout<<sizeof(A1)<<endl;	//4
    	cout<<ABig<<endl;		//4294967295
    	cout<<sizeof(B1)<<endl;	//8
    	cout<<BBig<<endl;		//68719476735
    }
    
    1. 由于枚举类型被设计为常量数值的“别名”,所以枚举常量总是可以被隐式转换为整型,且用户无法为枚举常量定义类型。

    C++11对传统枚举类型进行了扩展。

    底层的基本类型可以在枚举名称后加上":type",其中type可以是除wchar_t以外的任何整型,比如:

    enum Type:char{Low,Middle,High};
    

    强枚举类型

    使用enum class/struct

    强类型枚举具有如下几个优点:
    (1)强作用域,强类型枚举成员的名称不会被输出到其父作用域,所以不同枚举类型定义同名枚举成员编译不会报重定义错误。进而使用枚举类型的枚举成员时,必须指明所属范围,比如Enum::VAL1,而单独的VAL1则不再具有意义;
    (2)转换限制,强类型枚举成员的值不可以与整型发生隐式相互转换。比如比如Enumeration::VAL4==10;会触发编译错误;

    enum class Status { Ok, Error };
    enum struct Status2 { Ok, Error };
    
    //Status flag1 = 10; // err,无法隐式转换为int类型
    //Status flag2 = Ok; // err,必须使用强类型名称
    Status flag3 = Status::Ok;
    

    (3)可以指定底层类型。强类型枚举默认的底层类型是int,但也可以显示地指定底层类型。具体方法是在枚举名称后面加上":type",其中type可以是除wchar_t以外的任何整型。比如:

    enum class Type:char{Low,Middle,High};
    

    移动语义,右值引用与完美转发

    形参总是左值,即是型别是右值引用

    C++引入右值引用之后,可以通过右值引用,充分使用临时变量,或者即将不使用的变量即右值的资源,减少不必要的拷贝,提高效率

    三种引用

    • 左值引用
      • 形如T&
    • 右值引用
      • 若型别声明不准确具备T&&形式,或者型别推导未发生(全特化),则T&&代表右值引用
    • 万能引用
      • 当函数模板形参具备T&&型别,且T型别是由推导而来,或对象使用auto&&声明型别时,该形参或对象就是万能引用
      • 用左值初始化万能引用得到左值引用;用右值初始化万能引用得到右值引用

    右值引用常用于移动构造函数,移动构造函数的调用时机是:用同类的右值对象初始化新对象。那么,用当前类的左值对象(有名称,能获取其存储地址的实例对象)初始化同类对象时,是否就无法调用移动构造函数了,C++给出了解决方案:实现移动语义的move

    简单来说:拷贝语义(调用拷贝构造函数),移动语义(调用移动构造函数)

    std::move() 实现原理:

    1. 利用引用折叠原理将右值经过 T&& 传递类型保持不变还是右值,而左值经过 T&& 变为普通的左值引用,以保证模板可以传递任意实参,且保持类型不变;
    2. 然后通过 remove_refrence 移除引用,得到具体的类型 T;
    3. 最后通过 static_cast<> 进行强制类型转换,返回 T&& 右值引用。
    • std::move
      • 实施无条件的向右值型别的强制型别转换,实际不会执行移动操作
    • std::forward
      • 仅当传入实参被绑定到右值时,std::forward才针对该实参实施向右值型别的强制类型转换

    有的时候,我们需要将一个函数中某一组参数原封不动的传递给另一个函数,这里不仅仅需要参数的值不变,而且需要参数的类型属性(左值/右值)保持不变-完美转发;

    首先由于C++引入了万能引用,既可以接收右值,也可以接收左值,所以参数即是用到万能引用

    template <typename T>
    void function(T&& t) {
        otherdef(t);
    }
    
    int n = 10;
    int & num = n;
    function(num); // T 为 int&
    int && num2 = 11;
    function(num2); // T 为 int &&
    

    C++ 11标准为了更好地实现完美转发,特意为其指定了新的类型匹配规则,又称为引用折叠规则(假设用 A 表示实际传递参数的类型):

    • 当实参为左值或者左值引用(A&)时,函数模板中 T&& 将转变为 A&(A& && = A&);
    • 当实参为右值或者右值引用(A&&)时,函数模板中 T&& 将转变为 A&&(A&& && = A&&)

    即在实现完美转发时,只要函数模板的参数类型为 T&&,则 C++ 可以自行准确地判定出实际传入的实参是左值还是右值。

    但无论传入的形参是左值还是右值,对于函数模板内部来说,形参既有名称又能寻址,因此它都是左值;所以还需要解决一个问题,如何将左值,右值属性一起传递给调用函数,C++11又给出了方案:用forward将参数的原本左右值属性传递给被调用函数

    //重载被调用函数,查看完美转发的效果
    void otherdef(int & t) {
        cout << "lvalue
    ";
    }
    void otherdef(const int & t) {
        cout << "rvalue
    ";
    }
    
    //实现完美转发的函数模板
    template <typename T>
    void function(T&& t) {
        otherdef(forward<T>(t));
    }
    
    int main()
    {
        function(5);
        int  x = 1;
        function(x);
        return 0;
    }
    //输出
    //rvalue
    //lvalue
    

    类型推导

    模板类型推导

    //函数模板
    template<tyepname T>
    void f(ParamType param);
    f(expr);//一次调用
    
    • 情况1:ParamType是个指针或引用,但不是万能引用
      • 若expr具有引用型别,先将引用型别忽略
        • 需注意容器operator[]会返回T&,但由于模板推导&会被忽略导致左值变右值
      • 然后对expr与ParamType型别进行模式匹配,决定T型别
    • 情况2:ParamType是个万能引用
      • 若expr是个左值,T和ParamType都会被推导为左值引用
      • 若expr是个右值,则引用情况1规则
    • 情况3:Param既非指针也非引用,而是按值传递
      • 在传入参数时会进行拷贝,即param是一个传入对象的副本
      • 从而去除引用性,常量const性,与volatile
        • 若传入const char* const ptr,则param将会被推导为const char*,即自身const去掉,ptr指涉对象的常量性得以保留
      • 数组与指针
        • 当传入型别为数组const char name[]时,而模板定义为void(T param);则T将会被推导为指针型别const char*
        • 当传入型别为数组const char name[]时,而模板定义为void(T& param);则T将会被推导为实际数组型别const char [],这个型别会包含数组尺寸,从而可用来推导数组大小

    auto

    auto与模板类型推导真正唯一的区别在于,auto会假定用大括号括起来的初始化表达式代表一个std::initializer_list,但模板型别推导不会

    auto x={233};//型别为std::initializer_list<int>,值为{233}
    template<typename T>
    void f(T param);
    f({233});	//无法推导T型别
    

    C++14允许auto说明函数返回值需要推导,以及lambda里形参类型可声明为auto;但这两处auto用的是模板型别推导,即无法推导大括号

    decltype

    绝大多数情况下,decltype就是鹦鹉学舌,变量或表达式是啥型别就得出啥型别

    C++11中decltype主要用于声明返回值型别依赖于形参型别的函数模板

    //C++11 auto无实际意义,只是说明这里是返回值型别尾序语法
    template<typename Container,typename Index>
    auto authAndAccess(Container&c,Index i)
    	->decltype(c[i])
    {
    	authenticateUser();
    	return c[i];	
    }
    
    //C++14auto说明会发生型别推导
    template<typename Container,typename Index>
    auto authAndAccess(Container&c,Index i)
    {
    	authenticateUser();
    	return c[i];	
    }
    //当这里是错的,容器operator[]会返回T&,而auto使用的是模板推导,会去掉&;正确写法如下
    //推导过程采用decltype的规则,则不会去掉一些特性
    template<typename Container,typename Index>
    decltype(auto) authAndAccess(Container&c,Index i)
    {
    	authenticateUser();
    	return c[i];	
    }
    

    decltype的特殊情况

    decltype(auto) f1()
    {
    	int x=0;
    	return x;//返回int
    }
    decltype(auto) f1()
    {
    	int x=0;
    	return (x);//返回int&,且是局部变量的引用,将产生未定义行为
    }
    
    auto varname = value; //auto的语法格式
    decltype(exp) varname [= value]; //decltype的语法格式
    

    auto与decltype

    • decltype 会保留 cv 限定符,而 auto 有可能会去掉 cv 限定符。
      • 如果表达式的类型不是指针或者引用,auto 会把 cv 限定符直接抛弃,推导成 non-const 或者 non-volatile 类型。
      • 如果表达式的类型是指针或者引用,auto 将保留 cv 限定符。
    • decltype 会保留引用类型,而 auto 会抛弃引用类型,直接推导出它的原始类型

    C++17

    std::any

    引入了any,定义在any头文件中:#include <any>
    是一个可用于任何类型单个值的类型安全的容器.

    • std::any a = 1;: 声明一个any类型的容器,容器中的值为int类型的1
    • a.type(): 得到容器中的值的类型
    • std::any_cast(a);: 强制类型转换, 转换失败可以捕获到std::bad_any_cast类型的异常
    • has_value(): 判断容器中是否有值
    • reset(): 删除容器中的值
    • std::any_cast(&a): 强制转换得到容器中的值的地址
  • 相关阅读:
    NET Core-TagHelper实现分页标签
    NET Core-学习笔记(三)
    NET Core-学习笔记(二)
    NET Core-学习笔记(一)
    MVC默认路由实现分页-PagerExtend.dll
    Tomcat优化
    JVM参数配置大全
    tomcat8+memcached session共享
    Tomcat+Nginx+Redis+MySQL实现反向代理、负载均衡、session共享
    搭建Tomcat应用服务器、tomcat虚拟主机及Tomcat多实例部署
  • 原文地址:https://www.cnblogs.com/AMzz/p/14731535.html
Copyright © 2020-2023  润新知