美文网首页
c++11 新特性

c++11 新特性

作者: 王王王王王景 | 来源:发表于2019-07-29 16:01 被阅读0次

    c++11的新特性

    1.1 lambda表达式

    lambda表达式讲解

    [capture list] (params list) mutable exception-> return type { function body };
    [capture list] (params list) -> return type {function body};  //1
    [capture list] (params list) {function body};       //2
    [capture list] {function body};     //3
    

    例子1:

    vector<int> v({1,5,2,7,8});
    sort(v.begin(), v.end(), [] (const int &a, const int &b) { return a < b; });
    for(int i = 0; i < v.size(); ++i)
       cout<<v[i]<<endl;
    输出:
    1
    2
    5
    7
    8
    

    例子2:

    // 值捕获
    int main()
    {
        int a = 123;
        auto f = [a] { cout << a << endl; }; 
        a = 321;
        f(); // 输出:123
    }
    
    // 引用捕获
    int main()
    {
        int a = 123;
        auto f = [&a] { cout << a << endl; }; 
        a = 321;
        f(); // 输出:321
    }
    
    // 隐式值捕获
    int main()
    {
        int a = 123;
        auto f = [=] { cout << a << endl; };    // 值捕获
        f(); // 输出:123
    }
    
    // 隐式引用捕获
    int main()
    {
        int a = 123;
        auto f = [&] { cout << a << endl; };    // 引用捕获
        a = 321;
        f(); // 输出:321
    }
    

    C++11中的Lambda表达式捕获外部变量主要有以下形式:

    捕获形式 说明
    [] 不捕获任何外部变量
    [变量名, …] 默认以值得形式捕获指定的多个外部变量(用逗号分隔),如果引用捕获,需要显示声明(使用&说明符)
    [this] 以值的形式捕获this指针
    [=] 以值的形式捕获所有外部变量
    [&] 以引用形式捕获所有外部变量
    [=, &x] 变量x以引用形式捕获,其余变量以传值形式捕获
    [&, x] 变量x以值的形式捕获,其余变量以引用形式捕获

    1.2 自动类型推导和 decltype

    自动类型推导:
    auto x = 0; //0 是 int 类型,所以 x 也是 int 类型  
    auto c = 'a'; //char  
    auto d = 0.5; //double  
    auto national_debt = 14400000000000LL;//long long  
    vector<int> vi;
    auto ci=vi.begin();
    
    auto作为函数返回值时,只能用于定义函数,不能用于声明函数。
    #pragma once
    class test
    {
    public:
        auto testWork(int a, int b); // 声明函数(错误)
        // 在引用头文件的调用testWork函数是,编译无法通过。
    }
    
    class test
    {
    public:
        auto testWork(int a, int b) // 定义函数(正确)
        {
            return a+b;
        }
        // 但如果把实现写在头文件中,可以编译通过,
        // 因为编译器可以根据函数实现的返回值确定auto的真实类型。 
        
    }
    

    C++11 也提供了从对象或表达式中“俘获”类型的机制,
    新的操作符 decltype 可以从一个表达式中“俘获”其结果的类型并“返回”:
    decltype使用:

    const vector<int> vi;  
    typedef decltype (vi.begin()) CIT;  
    CIT another_const_iterator; 
    
    const int ci = 0, &cj = ci;
    decltype(ci) x = 0;
    decltype(cj) y = x;
    decltype(cj) z; //报错,因为cj是一个引用,因此作为引用的 z 必须要进行初始化
    
    
    int i = 0;
    decltype((i)) a; //报错,因为a类型为 int&,必须进行初始化
    decltype(i) b; //正确
    

    需要注意的是,decltype((variable))的结果永远是引用,而decltype(variable)结果只有当variable本身就是一个引用时才是引用,其实就是根据它的类型决定;

    1.3 deleted 函数和 defaulted 函数

    =default; 指示编译器生成该函数的默认实现。这有两个好处:一是让程序员轻松了,少敲键盘,二是有更好的性能。
    与 defaulted 函数相对的就是 deleted 函数, 实现 non copy-able 防止对象拷贝,要想禁止拷贝,用 =deleted 声明一下两个关键的成员函数就可以了:

    int func()=delete;  
    //防止对象拷贝的实现
    struct NoCopy  
    {  
        NoCopy & operator =(const NoCopy &) = delete;  
        NoCopy(const NoCopy &) = delete;  
    };  
    NoCopy a;  
    NoCopy b(a); //编译错误,拷贝构造函数是 deleted 函数 
    

    1.4 nullptr

    nullptr 是一个新的 C++ 关键字,它是空指针常量,它是用来替代高风险的 NULL 宏和 0 字面量的。
    nullptr 是强类型的,所有跟指针有关的地方都可以用 nullptr,包括函数指针和成员指针:

    void f(int); //#1  
    void f(char *);//#2  
    //C++03  
    f(0); //调用的是哪个 f?  
    //C++11  
    f(nullptr) //毫无疑问,调用的是 #2  
     
    const char *pc=str.c_str(); //data pointers  
    if (pc != nullptr)  
      cout << pc << endl;  
    int (A::*pmf)()=nullptr; //指向成员函数的指针  
    void (*pmf)()=nullptr; //指向函数的指针  
    

    1.5 右值引用

    右值引用讲解

    左值右值的区别:

    • 左值:在赋值号左边,可以被赋值的值,可以取地址;

    • 右值:在赋值号右边,取出值赋给其他变量的值;

    • 左值引用:type & 引用名 = 左值表达式

    • 右值引用:type && 引用名 = 右值表达式

    有一个可以区分左值和右值的便捷方法:
    看能不能对表达式取地址,如果能,则为左值,否则为右值。

    int main() {
        int i    = 1;          //i为常规左值
        int &r   = i;          //正确:r绑定到i上,r是一个引用
        int &&rr = i;          //错误:不能将一个右值引用绑定到左值i上
        int &r2  = i * 1;      //错误:等号右边是一个右值,但左值引用只能绑定到左值上
        int &&rr2 = i * 1;     //正确:右值引用绑定到右值上
        const int &r3 = i * 1; //正确:可以将一个const的左值引用绑定到右值上
        return 0;
    }
    

    关于右值引用的两个应用:
    1.移动构造
    2.移动赋值

    MyString(const MyString& str) // 拷贝构造函数
    MyString(MyString&& str) // 移动构造函数
    MyString& operator=(const MyString& str) // 拷贝赋值函数 =号重载
    MyString& operator=(MyString&& str) // 移动赋值函数 =号重载
    
    // 我们可以销毁一个move后源对象,也可以赋予它新值,但不能使用一个move后源对象的值
    
    // 移动构造函数,参数 "arg.member" 是左值
    A(A&& arg) : member(std::move(arg.member))
    {
    } 
     
    // 移动赋值函数
    A& operator=(A&& other) {
         member = std::move(other.member);
         return *this;
    }
    

    移动构造函数与拷贝构造函数的区别是,拷贝构造的参数是const MyString& str,是常量左值引用,而移动构造的参数是MyString&& str,是右值引用,而MyString("hello")是个临时对象,是个右值,优先进入移动构造函数而不是拷贝构造函数。而移动构造函数与拷贝构造不同,它并不是重新分配一块新的空间,将要拷贝的对象复制过来,而是"偷"了过来,将自己的指针指向别人的资源,然后将别人的指针修改为nullptr,这一步很重要,如果不将别人的指针修改为空,那么临时对象析构的时候就会释放掉这个资源,"偷"也白偷了。

    对于一个左值,肯定是调用拷贝构造函数了,但是有些左值是局部变量,生命周期也很短,能不能也移动而不是拷贝呢?C++11为了解决这个问题,提供了std::move()方法来将左值转换为右值,从而方便应用移动语义。我觉得它其实就是告诉编译器,虽然我是一个左值,但是不要对我用拷贝构造函数,而是用移动构造函数吧。。。

    vector<MyString> vecStr2;
        vecStr2.reserve(1000); //先分配好1000个空间
        for(int i=0;i<1000;i++){
            MyString tmp("hello");
            vecStr2.push_back(std::move(tmp)); //调用的是移动构造函数
        }
    

    1.6 智能指针

    shared_ptr    
    weak_ptr 
    unique_ptr
    

    1.7 多线程

    在C++11以前,C++的多线程编程均需依赖系统或第三方接口实现,一定程度上影响了代码的移植性。C++11中,引入了boost库中的多线程部分内容,形成C++标准,形成标准后的boost多线程编程部分接口基本没有变化,这样方便了以前使用boost接口开发的使用者切换使用C++标准接口,把容易把boost接口升级为C++接口。我们通过如下几部分介绍C++11多线程方面的接口及使用方法。

    1.7.1 std::thread

    std::thread为C++11的线程类,使用方法和boost接口一样,非常方便,同时,C++11的std::thread解决了boost::thread中构成参数限制的问题,我想着都是得益于C++11的可变参数的设计风格。我们通过如下代码熟悉下std::thread使用风格。

    //示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
    #include <thread>
    void threadfun1()
    {
        std::cout << "threadfun1 - 1\r\n" << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
        std::cout << "threadfun1 - 2" << std::endl;
    }
    
    void threadfun2(int iParam, std::string sParam)
    {
        std::cout << "threadfun2 - 1" << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(5));
        std::cout << "threadfun2 - 2" << std::endl;
    }
    
    int main()
    {
        std::thread t1(threadfun1);
        std::thread t2(threadfun2, 10, "abc");
        t1.join();
        std::cout << "join" << std::endl;
        t2.detach();
        std::cout << "detach" << std::endl;
    }
    

    输出:

    threadfun1 - 1
    threadfun2 - 1
    
    threadfun1 - 2
    join
    detach
    

    有以上输出结果可以得知,t1.join()会等待t1线程退出后才继续往下执行(当thread::join()函数被调用后,调用它的线程会被block,直到线程的执行被完成。);t2.detach()此时 子线程和main thread 完全分离,两个线程自顾自的运行,main thread可以不等子线程运行完,就提前结束。detach字符输出后,主函数退出,threadfun2还未执行完成,但是在主线程退出后,t2的线程也被已经被强退出。

    1.7.2 std::atomic

    std::atomic为C++11分装的原子数据类型。

    • 什么是原子数据类型?
      从功能上看,简单地说,原子数据类型不会发生数据竞争,能直接用在多线程中而不必我们用户对其进行添加互斥资源锁的类型。从实现上,大家可以理解为这些原子类型内部自己加了锁。

    下面例子中,我们使用10个线程,把std::atomic_int类型的变量iCount从100减到1。

    //示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
    #include <thread>
    #include <atomic>
    #include <stdio.h>
    std::atomic_bool bIsReady = false; // std::atomicM<bool>
    std::atomic_int iCount = 100;
    void threadfun1()
    {
        if (!bIsReady) {
            std::this_thread::yield();
        }
        while (iCount > 0)
        {
            printf("iCount:%d\r\n", iCount--);
        }
    }
    
    int main()
    {
        std::atomic_bool b;
        std::list<std::thread> lstThread;
        for (int i = 0; i < 10; ++i)
        {
            lstThread.push_back(std::thread(threadfun1));
        }
        for (auto& th : lstThread)
        {
            th.join();
        }
    }
    

    1.7.3 std::condition_variable

    C++11中的std::condition_variable就像Linux下使用pthread_cond_wait和pthread_cond_signal一样,可以让线程休眠,直到别唤醒,现在在从新执行。线程等待在多线程编程中使用非常频繁,经常需要等待一些异步执行的条件的返回结果。

    // webset address: http://www.cplusplus.com/reference/condition_variable/condition_variable/%20condition_variable
    // condition_variable example
    #include <iostream>           // std::cout
    #include <thread>             // std::thread
    #include <mutex>              // std::mutex, std::unique_lock
    #include <condition_variable> // std::condition_variable
    
    std::mutex mtx;
    std::condition_variable cv;
    bool ready = false;
    
    void print_id(int id) {
        std::unique_lock<std::mutex> lck(mtx);
        while (!ready) cv.wait(lck);
        // ...
        std::cout << "thread " << id << '\n';
    }
    
    void go() {
        std::unique_lock<std::mutex> lck(mtx);
        ready = true;
        cv.notify_all();
    }
    
    int main()
    {
        std::thread threads[10];
        // spawn 10 threads:
        for (int i = 0; i<10; ++i)
            threads[i] = std::thread(print_id, i);
    
        std::cout << "10 threads ready to race...\n";
        go();                       // go!
    
        for (auto& th : threads) th.join();
    
        return 0;
    }
    

    上面的代码,在14行中调用cv.wait(lck)的时候,线程将进入休眠,在调用33行的go函数之前,10个线程都处于休眠状态,当22行的cv.notify_all()运行后,14行的休眠将结束,继续往下运行,最终输出如上结果。

    1.8 std::function、std::bind封装可执行对象

    std::bind和std::function也是从boost中移植进来的C++新标准,这两个语法使得封装可执行对象变得简单而易用。此外,std::bind和std::function也可以结合我们一下所说的lamda表达式一起使用,使得可执行对象的写法更加“花俏”。

    我们下面通过实例一步步了解std::function和std::bind的用法:
    Test.h

    //Test.h 示例代码
    class Test
    {
    public:
        void Add()
        {
            
        }
    };
    
    
    //main.cpp 示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
    #include <functional>
    #include <iostream>
    #include "Test.h"
    int add(int a,int b)
    {
        return a + b;
    }
    
    int main()
    {
        Test test;
        test.Add();
        return 0;
    }
    

    假如我们的需求是让Test里面的Add由外部实现,如main.cpp里面的add函数,有什么方法呢?

    //修改Test.h
    class Test
    {
    public:
        typedef int(*FunType)(int, int);
        void Add(FunType fun,int a,int b)
        {
            int sum = fun(a, b);
            std::cout << "sum:" << sum << std::endl;
        }
    };
    
    //main.cpp
    int add(int a,int b)
    {
        return a + b;
    }
    ....
    ....
    Test test;
    test.Add(add, 1, 2);
    ....
    

    到现在为止,完美了吗?如果你是Test.h的提供者,你觉得有什么问题?我们把问题升级,假如add实现是在另外一个类内部,如下代码:

    //示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
    class TestAdd
    {
    public:
        int Add(int a,int b)
        {
            return a + b;
        }
    };
    
    int main()
    {
        Test test;
        //test.Add(add, 1, 2);
        return 0;
    }
    

    假如add方法在TestAdd类内部,那你的Test类没辙了,因为Test里的Test函数只接受函数指针。

    //继续修改Test.h
    class Test
    {
    public:
        void Add(std::function<int(int, int)> fun, int a, int b)
        {
            int sum = fun(a, b);
            std::cout << "sum:" << sum << std::endl;
        }
    };
    // Test类中std::function<int(int,int)>表示std::function封装的可执行对象返回值和两个参数均为int类型。
    
    
    main.cpp
    int add(int a,int b)
    {
        std::cout << "add" << std::endl;
        return a + b;
    }
    
    class TestAdd
    {
    public:
        int Add(int a,int b)
        {
            std::cout << "TestAdd::Add" << std::endl;
            return a + b;
        }
    };
    
    int main()
    {
        Test test;
        test.Add(add, 1, 2);
    
        TestAdd testAdd;
        test.Add(std::bind(&TestAdd::Add, testAdd, std::placeholders::_1, std::placeholders::_2), 1, 2);
        return 0;
    }
    

    解释:

    • std::bind第一个参数为对象函数指针,表示函数相对于类的首地址的偏移量;

    • testAdd为对象指针;

    • std::placeholders::_1和std::placeholders::_2为参数占位符,表示std::bind封装的可执行对象可以接受两个参数。

    相关文章

      网友评论

          本文标题:c++11 新特性

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