美文网首页
C++ 11 新特性汇总

C++ 11 新特性汇总

作者: SwordShield | 来源:发表于2018-03-03 22:25 被阅读54次

    内容引自《写给大忙人看的C++》,未包含多线程相关话题,但是基本上C++最重要的方面都包括在内了

    概要

    • 改进的对象构造(类)
    • 其它核心语言增强
    • 新的关键字
    • 对C++库的扩展

    B.1 对象结构改进

    C++提供了改进的技术用于声明类和编写构造函数,包括:

    • 继承基类的构造函数
    • 默认的成员值
    • 委托构造函数
    • override关键字
    • final关键字
    • 统一的初始化

    1.继承基类的构造函数

    子类继承基类的所有构造函数,语法:

    using 基类:基类;
    

    在派生类中,这个声明会导致这个类从基类继承所有的构造函数。而且派生类仍然可以添加自己的额外的构造函数和重载继承的构造函数。


    例子:

    #include <string>
    
    enum sex {male,female};
    
    class Mammal {
    public:
        std::string name;
        double avg_life_span;
        sex gender;
        Mammal() : name(""),avg_life_span(0.0),
                gender(female) {}
        Mammal(std::string nm,double lf,sex gn):name(nm),
                avg_life_span(lf),gender(gn){}
    };
    
    class Dog : public Mammal{
    public:
        std::string breed;
        using Mammal::Mammal;
    };
    

    2.默认的成员值

    可以在声明数据成员的时候初始化它们,给它们默认值。构造函数仍然可以覆盖这些值。

    这种初始化对于具有许多构造函数的类特别有用。每个构造函数都可以覆盖一个数据成员的默认值,其它值用声明时的默认值。

    例子

    class Point {
    public:
        double x = 0.0;
        double y = 0.0;
        //构造函数,依然有效
        Point (double new_x,double new_y)
            : x(new_x),y(new_y)
    };
    

    3.委托构造函数

    委托构造函数调用另一个构造函数来做它的一部分或全部工作。这使你能够充分利用已有的编程工作,从而导致较少的代码重复。

    4.override关键字

    该功能可以防止C++因你试图重载一个继承函数,但拼写名字错误创建一个新的不必要的成员函数

    5.final 关键字

    此关键字阻止函数被应用它的类重载(一般用在派生类,防止派生类的派生类继续重载)

    6.统一的初始化

    所有的数据项,包括一个类的实例(即对象),现在可以利用花括号来代替圆括号进行初始化,例如"Point pt1{0,1};"。

    初始化数据的老办法仍然支持,但这种花括号的统一使用使声明的语法更加一致。

    B2 其它核心语言增加功能

    • 基于范围的for循环
    • long long int 类型
    • lambda函数
    • 移动主义和右值引用
    • 强类型枚举(枚举)
    • 智能指针
    • 原始字符串字面量
    • 静态断言
    • 可变参数模板
    • 改善了右尖括号的处理

    1.基于范围的for循环

    基于范围的for语句有两个版本:使用引用变量的版本:

    int my_array[10] = {1,2,3,4,5,6,7,8,9,10};
    for(int &i: my_array)
    {
        i=0;
    }
    

    不使用引用变量的版本

    int my_array[10] = {1,2,3,4,5,6,7,8,9,10};
    for(int i: my_array)
    {
        //这里不能改变i的值,下面一句会报错
        i=0;
        //只能访问
        cou << i << "";
    }
    

    2.long long int 类型

    这是一个超长整数类型,通长是64位

    3.lambda函数

    lambda函数是一个匿名函数,通长在使用它的地方定义。

    在使用标准模板库的容器和算法时,你可能经常需要“随时”提供一个很短的函数定义,此功能特别有用

    3.1 基本语法:

    //编译器隐匿的确定返回类型:
    [闭包] (参数) { 语句组 }
    
    //用return明确指定返回类型:
    [闭包] (参数) -> 返回类型 { 语句组 }
    
    
    [] () -> double { return 10; }
    

    与下面的语句效果是一样的:

    [] () { return 10.0; }
    

    使用lambda函数的最简单方法之一是将其存储在一个变量中,然后就像使用一个函数名一样使用该变量,这里就需要 auto 关键字了:

    int i = 1;
    int j = 2;
    auto f = [] (int a, int b) { return a+b; } ;
    cout << f(i,j) << endl;
    

    [] 内的的值包括:

    • &:[&] 或者 [&a]
      修饰按引用访问变量,如果后面不加变量,只是一个&,表示周围作用域内的变量都按引用访问,并且lambda函数中可以修改这些变量,修改是永久性的!
    • = :[=]
      所有在周围的局部变量都可以按值访问,不能修改
    • 参数:[&a,&b,x,y]
      参数是变量的列表,每个参数都可以加&,或者不加
    • [=,&sum] :
      其它变量按值访问,sum按引用访问
    • [&,y],[参数,&]

    3.2 在STL中使用

    int arr[5] = {5,10,15,20,25};
    for_each(arr, arr + 5, [](int n) { cout << n << " "; } );
    

    4.移动语义和右值引用

    移动语义通过在适当的情况下把复制操作替换为数据所有权的简单转移,来显著提高程序的性能。
    而右值引用使你能够为自己的类实现移动语义。

    移动语义:

    std::move函数使得它的参数被解释为一个右值,std::move其实是一个强制类型转换,效果是强制使用移动语义,在使用swap模板函数的情况 下,这非常有用:

    //sort 算法进行了大量的交换,每个交换都涉及三个完整的复制操作
    void swap(Type &A,Type &B)
    {
        Type temp = A;
        A = B; 
        B = temp;
    }
    //如果对象比较大,这将是大量的工作,特别是如果它必须被执行几千次时,如果“在这些交换中,在A和B之间仅仅转移所有权”将更高效(C++11版本):
    void swap(Type& A, Type &B)
    {
        Type temp = std::move(A);
        A = std::move(B);
        B - std::move(temp);
    }
    

    右值引用

    它应该只在函数的参数中使用,这里的函数主要是构造函数和赋值运算符函数,其语法为:

    类型&& 名称
    

    举个简单的例子:

    #include <cstring> //支持memcpy
    
    class MyString{
    public:
        char* ptr;
        int n_length;
        
        MyString() {
            ptr = new char;
            *ptr = '\0';
            n_length = 0;
        }
        
        MyString(const char* cstr){
            int n = n_length = strlen(cstr);
            ptr = new char[n+1];
            memcpy(ptr,cstr,n+1);
        }
        
        //这个拷贝构造函数必须分配新的内存,然后从它的源中复制所有数据
        MyString(const MyString& other){
            int n = n_length = other.n_length;
            ptr = new char[n+1];
            memcpy(ptr,other.ptr,n+1);
        }
        
        //新的移动构造函数(move construtor),只是转移一个整数和几个指针的值,仅仅当参数是一个右值(如临时对象)的时候才会调用它
        MyString(MyString&& other){
            ptr = other.ptr;
            n_length = other.n_length;
            other.ptr = nullptr;
            std::cout<<"I'm in MyString&& constructor"<<std::endl;
        }
        
        //标准赋值运算符
        MyString& operator=(const MyString& other){
            if(ptr){
                delete ptr;
            }
            int n = n_length = other.n_length;
            ptr = new char[n+1];
            memcpy(ptr,other.ptr,n+1);
            return *this;
        }
        
        //右值重载赋值运算符(=)
        MyString& operator = (MyString&& other){
            ptr = other.ptr;
            n_length = other.n_length;
            other.ptr = nullptr;
            return *this;
        }
        
        //注意:在标准(左值引用)版本中,参数声明中使用了const,右值引用的版本中不能使用const,因为它做的事情之一就是把源对象的指针置空。
    };
    

    下面的语句将使用构造函数的右值引用版本:

    MyString s = MyString("White Rabbit"); //包含右值
    

    运行下面的测试代码来看在使用和不使用右值时的速度:

    int t1 = clock(); //<ctime>
    vector<MyString> vec(5000);
    
    for(int i=0; i<5000; i++)
    {
        vec.push_back(MyString("Happy days are here again...!");
    }
    
    random_shuffle(vec.begin(),vec.end());
    int t2 = clock();
    cout<<"Elapsed time was:"<< t2 - t1 << endl;
    

    使用右值的版本大约是左值版本的3倍

    ** 右值和包含的对象 **
    虽然std::move 函数(实际上是一个强制类型转换)必须被小心使用,但在其它一些情况下,它是非常有用的。假设你有一个包含对象的类:

    class FullName{
    public:
        MyString first_name;
        MyString middle_name;
        MyString last_name;
    };
    

    你可能会认为,因为MyString类能够使用移动语义,所以FullName也能使用移动语义。但是要获得这种优化,你需要给FullName自身提供右值引用的函数:

    //为了全面优化一个类,你需要 为将受益于这种优化的每个成员 都启用移动语义
    FullName(FullName&& other){
        first_name = std::move(other.fisrt_name);
        middle_name = std::move(other.middle_name);
        last_name = std::move(other.last_name);
    }
    

    5.强类型枚举(枚举)

    这项功能对使用enum 关键字声明的类型增加了类型检查,使得它们是类型安全的。

    6.智能指针

    shared_ptr和unique_ptr类型(取代不用的auto_ptr)提供了一种改进的智能指针实现,它会在动态内存超出作用域时自动释放它们,有助于防止内存泄漏。

    7.原始字符串字面量

    C++提供了R前缀,表明将不会识别转义序列,字符串将按照它在源码中出现的样子进行存储:

    cout<< R"C:\root\bin\n\readme.txt";
    //会打印
    C:\root\bin\n\readme.txt
    如果没有原始字符串的功能,代码只能这么写:
    cout<< "C:\\root\\bin\\n\\readme.txt";
    

    8.静态断言

    static_assert 关键字提供了一种在编译时如果必要条件未满足则对错误打标记的办法。使用模板时,这是特别有用的

    9.可变参数模板

    此功能允许你编写采用任意数量的参数的模板,实现步骤:

    1. 编写一个终端版本的函数,通常只有一个参数
    2. 编写该函数的可变参数模板版本。当第一个版本(终端版本)不能用通常是因为有多个参数时,这个版本被实例化。
    3. 从可变参数模板中展开多参数列表,从而使模板少用一个参数来调用自己。

    考虑下面的例子:

    int i=10,j=20;
    printv("i=",i,",j=",j,".");
    //这将打印如下内容:
    i=10,j=20.
    

    上面函数的美妙之处在于它没有规定参数列表大小的实际限制。下面是这个模板:

    //终端版本,当有一个参数时调用
    template<typename T>
    void printv(T val){
        std::cout << val << std::endl;
    }
    
    //可变参数版本:有多个参数时调用 
    template<typename T,typename... TArgs>
    void printv(T val,TArgs... args){
        std::cout<<val;     //打印第一个参数
        printv(args...);    //传递其余参数
    }
    

    这有点类似于递归调用

    改善了右尖括号的处理

    之前连续使用两个 右括号(>>)时,它被曲解为右移痊运算符,例如"vector<list<int>>",这种行为已经得到修复。

    B.3 其它关键字

    • auto
    • constexpr(使一个变量被当作 一个编译时常是)
    • decltype(返回一个表达式的类型,其结果可像一个类型名称那样用于声明变量、参数或返回值的类型)
    • nullptr (可以把指针设置为“不指向任何地方 ”),这是对的NULL宏的一个的替代。
    • thread_local

    thread_local

    该关键字给出了一个变量线程局部存储类,以便在多线程程序中每个线程都有自己的变量副本。

    B.4 标准库的扩展

    • STL容器的列表初始化
    • 随机库
    • 正则表达式库
    • 无序(表驱动的)容器
    • 附加 的算法
    • 元组模板
    • 其它新类型

    STL容器的列表初始化

    STL容器现在可以直接 从(大括号 括起来的)聚合列表初始化,就像数组那样。这消除了为了初始化容器就到处调用 push_back的需要。

    随机库

    新的随机库代了旧式C语言库rand和srand函数。这个新的库提供了可选择的随机引擎,每一个都提供了速度 和质量之间不同的平衡,也提供了一个大的分布的选择。每个分布都进行了优化,以在目标范围内根据我写的概率函数产生随机数。

    正则表达式库

    该 库提供了一组强大 的查找和替换功能用于分析 和处理文本字符串。

    无序(表驱动的)容器

    新的C++11容器 对应 头文件
    unordered_map map <unordered_map>
    unordered_multimap multimap <unordered_map>
    unordered_set set <unordered_set>
    unordered_multiset multiset <unordered_set>

    新的无序map和set容器。它们中的每一个都提供了用一个键值来快速查找相关元素的方便的数据字典或简陋的数据库。优点是,它们利用散列表技术提供更快的查找速度,当然,也具有一定的局限性。

    新的C++11容器 对应 头文件
    unordered_map map <unordered_map>
    unordered_multimap multimap <unordered_map>
    unordered_set set <unordered_set>
    unordered_multiset multiset <unordered_set>

    无序容器使用一个内部维护的散列表而不是一棵二叉树来查找元素。

    附加 的算法

    元组模板

    这是一个预定义的可变参数的类模板,它可以存储任意数量的任意类型的数据项。

    其它新类型

    包括 chrono,ratio和complex,分别支持时间测量,有理数(分数),和复数

    相关文章

      网友评论

          本文标题:C++ 11 新特性汇总

          本文链接:https://www.haomeiwen.com/subject/fmvxfftx.html