c++11_c++11标准 - CSDN
c++11 订阅
C++11标准是 ISO/IEC 14882:2011 - Information technology -- Programming languages -- C++ 的简称 [1]  。C++11标准由国际标准化组织(ISO)和国际电工委员会(IEC)旗下的C++标准委员会(ISO/IEC JTC1/SC22/WG21)于2011年8月12日公布 [2]  ,并于2011年9月出版。2012年2月28日的国际标准草案(N3376)是最接近于C++11标准的草案(仅编辑上的修正)。此次标准为C++98发布后13年来第一次重大修正。 展开全文
C++11标准是 ISO/IEC 14882:2011 - Information technology -- Programming languages -- C++ 的简称 [1]  。C++11标准由国际标准化组织(ISO)和国际电工委员会(IEC)旗下的C++标准委员会(ISO/IEC JTC1/SC22/WG21)于2011年8月12日公布 [2]  ,并于2011年9月出版。2012年2月28日的国际标准草案(N3376)是最接近于C++11标准的草案(仅编辑上的修正)。此次标准为C++98发布后13年来第一次重大修正。
信息
发布时间
2011年8月12日
标准版本
C++标准第三版
标准文件号
ISO/IEC 14882:2011
中文名
C++11
出版时间
2011年9月
外文名
C++11
c++11基本信息
C++11标准为C++编程语言的第三个官方标准,正式名叫ISO/IEC 14882:2011 - Information technology -- Programming languages -- C++ 。 [1]  在正式标准发布前,原名C++0x。它将取代C++标准第二版ISO/IEC 14882:2003 - Programming languages -- C++ 成为C++语言新标准。C++11包含了核心语言的新机能,并且拓展C++标准程序库,并且加入了大部分的C++ Technical Report 1程序库(数学上的特殊函数除外)。C++ 标准委员会计划在2010年8月之前完成对最终委员会草案的投票,以及于2011年3月3召开的标准会议完成国际标准的最终草案。最终于2011年8月12日公布,并于2011年9月出版。2012年2月28日的国际标准草案(N3376)是最接近于现行标准的草案(编辑上的修正)。此次标准为13年第一次重大修正。ISO将在2014年和2017年发布C++的后续版本 [3]  。
收起全文
精华内容
参与话题
  • 十大必掌握C++11新特性

    万次阅读 多人点赞 2017-04-19 14:11:23
     C++11,之前被称作C++0x,即ISO/IEC 14882:2011,是目前的C++编程语言的正式标准。它取代第二版标准ISO/IEC 14882:2003(第一版ISO/IEC 14882:1998发布于1998年,第二版于2003年发布,分别通称C++98以及C++03,两者...

      简介
      C++11,之前被称作C++0x,即ISO/IEC 14882:2011,是目前的C++编程语言的正式标准。它取代第二版标准ISO/IEC 14882:2003(第一版ISO/IEC 14882:1998发布于1998年,第二版于2003年发布,分别通称C++98以及C++03,两者差异很小)。新的标准包含了几个核心语言增加的新特性,而且扩展C++标准程序库,并入了大部分的C++ Technical Report 1程序库(数学的特殊函数除外)。最新的消息被公布在 ISO C++ 委员会网站(英文)。
      ISO/IEC JTC1/SC22/WG21 C++ 标准委员会计划在2010年8月之前完成对最终委员会草案的投票,以及于2011年3月召开的标准会议完成国际标准的最终草案。然而,WG21预期ISO将要花费六个月到一年的时间才能正式发布新的C++标准。为了能够如期完成,委员会决定致力于直至2006年为止的提案,忽略新的提案。最终,于2011年8月12日公布,并于2011年9月出版。2012年2月28日的国际标准草案(N3376)是最接近于现行标准的草案,差异仅有编辑上的修正。
      本文主要罗列C++11相比较旧版本有重大改变并且我们今后可能会频繁接触的一些功能。
      1、新增基于范围的for循环
      类似Java中foreach语句,为遍历数组提供了很大方便。

    int nArr[5] = {1,2,3,4,5};
    for(int &x : nArr)
    {
        x *=2;   //数组中每个元素倍乘
    }

      2、自动类型推断 auto
      它的作用就是当编译器在一个变量声明的时候,能够根据变量赋的值推断该变量的数据类型。这样就有些逼近Python中定义变量的功能,无需提前声明定义的变量的数据类型。例如:

    auto i = 1;    //编译器自动推断i为int类型

      这个功能在各种标准模板库容器中使用,作用更突出。如下:

    vector<int> vec(6,10);
    vector<int>::iterator iter = vec.iterator();
    auto iterAuto = vec.iterator(); //相比较上一句方便很多

      3、匿名函数 Lambda
      如果代码里面存在大量的小函数,而这些函数一般只被一两处调用,那么不妨将它们重构成Lambda表达式,也就是匿名函数。作用就是当你想用一个函数,但是又不想费神去命名一个函数。
      该功能函数实际上在其他面向对象语言中早就存在,例如Java,Python都定义了该功能。C++中Lambda表达式格式如下:

    [capture](params)->ret { body};

      ① [capture]指定在可见域范围内lambda表达式代码内可见的参数。例如:
    - [a, &b],前文定义的a以值方式被表达式捕获,b则是以引用的方式;
    - [this] 以值的方式捕获 this 指针。
    - [&] 以引用的方式捕获所有的外部自动变量。
    - [=] 以值的方式捕获所有的外部自动变量。
    - [] 不捕获外部的任何变量。
    【注】:const 类型的 lambda 表达式,该类型的表达式不能改捕获(“capture”)列表中的值。
      ② (params)指定lambda表达式内部变量定义。
      ③ ->ret是返回类型,如果 lambda 代码块中包含了 return 语句,则该 lambda 表达式的返回类型由 return 语句的返回类型确定。如果没有 return 语句,则类似 void f(…) 函数。
      ④ {body}是Lambda表达式主题结构。
      例句:

    auto func = [](int i){ return i+4};// 可以体会auto的好处了
    cout<< func(10) << endl;           //输出为14

      4、后置返回类型(tailng-return-type)

     template Ret adding_func(const Lhs &lhs, const Rhs &rhs) {return lhs + rhs;}

      这不是合法的C++,因为lhs和rhs还没定义;解析器解析完函数原型的剩余部分之前,它们还不是有效的标识符。
      为此, C++11引入了一种新的函数声明语法,叫做后置返回类型(trailing-return-type)。

    template auto adding_func(const Lhs &lhs, const Rhs &rhs) -> decltype(lhs+rhs) {return lhs + rhs;}

      这种语法可以用到更普通的函数声明和定义上:

    struct SomeStruct  {
        auto func_name(int x, int y) -> int;
    };
    auto SomeStruct::func_name(int x, int y) -> int {
        return x + y;
    }

      关键字auto的这种用法与在自动类型推导中有所不同。
      5、显示重写(覆盖)override和final
      在C++03中,很容易让你在本想重写基类某个函数的时候却意外地创建了另一个虚函数。例如:

    struct Base {
        virtual void some_func(float);
    }; 
    struct Derived : Base {
        virtual void some_func(int);
    };

      本来Derived::some_func函数是想替代Base中那个函数的。但是因它的接口不同,又创建了一个虚函数。这是个常见的问题,特别是当用户想要修改基类的时候。
      C++11引入了新的语法来解决这个问题:

    struct Base {
        virtual void some_func(float);
    }; 
    struct Derived : Base {
        virtual void some_func(int) override; // 病态的,不会重写基类的方法
    };

      override 这个特殊的标识符意味编译器将去检查基类中有没有一个具有相同签名的虚函数,如果没有,编译器就会报错!
      C++11还增加了防止基类被继承和防止子类重写函数的能力。这是由特殊的标识符final来完成的,例如:

    struct Base1 final { };
    
    struct Derived1 : Base1 { }; // 病态的, 因为类Base1被标记为final了
    
    struct Base2 {
        virtual void f() final;
    };
    
    struct Derived2 : Base2 {
        void f(); // 病态的, 因为虚函数Base2::f 被标记为final了.
    };

      在这个例子中, virtual void f() final;语句声明了一个虚函数却也阻止了子类重写这个函数。它还有一个作用,就是防止了子类将那个特殊的函数名与新的参数组合在一起。
      需要注意的是,override和final都不是C++语言的关键字。他们是技术上的标识符,只有在它们被用在上面这些特定的上下文在才有特殊意义。用在其它地方他们仍然是有效标识符。
      6、空指针常量 nullptr
      NULL通常在C语言中预处理宏定义为(void*)0或者0,这样0就有int型常量和空指针的双重身份。但是C++03中只允许0宏定义为空指针常量,这就会造成如下的错误:

    void foo(int n);
    void foo(char* cArr);

      上面声明了两个重载函数,当我调用foo(NULL),编译器将会调用foo(int)函数,而实际上我是想调用foo(char*)函数的。为了避免这个歧义,C++11重新定义了一个新关键字nullptr,充当单独空指针常量。
      7、long long int类型
      C++03中,最大的整数类型是long int。它保证使用的位数至少与int一样。 这导致long int在一些实现是64位的,而在另一些实现上却是32位的。C++11增加了一个新的整数类型long long int来弥补这个缺陷。它保证至少与long int一样大,并且不少于64位。这个类型早在C99就引入到了标准C中, 而且大多数C++编译器都以扩展的形式支持这种类型了。
      8、模板的别名
      在进入这个主题前,先弄清楚“模板”和“类型”的区别。类型,是具体的数据类型,可以直接用来定义变量。 模板,是类型的模板,根据这个模板可以产生具体的类型;模板是不能直接定义变量的;当指定了所有的模板参数后,就产生了一个具体的类型,就可以用来定义变量了。
      在C++03中,只能为类型(包括完全特化的模板,也是一种类型)定义别名,而不能为模板定义别名: 

    template <typename First, typename Second, int Third>
    class SomeType;
    template <typename Second>
    typedef SomeType<OtherType, Second, 5> TypedefName; // 在C++03中, 这是非法的.

      C++11增加为模板定义别名的能力,用下面这样的语法:

    template <typename First, typename Second, int Third>
    class SomeType; 
    template <typename Second>
    using TypedefName = SomeType<OtherType, Second, 5>;
    //这种using语法也可以用来定义类型的别名:  
    typedef void (*FunctionType)(double);  // 老式语法
    using FunctionType = void (*)(double); // 新式语法

      9、允许sizeof运算符可以再类型数据成员上使用,无需明确对象。

    struct p {otherClass member;};
    sizeof(p::member);

      10、线程支持
      C++11虽然从语言上提供了支持线程的内存模型,但主要的支持还是来自标准库。
      新的标准库提供了一个线程类(std::thread)来运行一个新线程,它带有一个函数对象参数和一系列可选的传递给函数对象的参数。通过std::thread::join()支持的线程连接操作可以让一个线程直到另一个线程执行完毕才停止。std:thread::native_handle()成员函数提供了对底层本地线程对象的可能且合理的平台相关的操作。
      为支持线程同步,标准库增加了互斥体(std::mutex, std::recursive_mutex等)和条件变量(std::condition_variable 和std::condition_variable_any)。这些都是通过RAII锁和加锁算法就可以简单使用的。
      有时为了高性能或底层工作,要求线程间的通信没有开销巨大的互斥锁。原子操作可以达到这个目的,这可以随意地为一个操作指定最小的内存可见度。显式的内存屏障也可以用于这个目的。
      C++11线程库还包含了futures和promises,用于在线程间传递异步结果。并且提供了std::packaged_task来封装可以产生这种异步结果的函数调用。
      更高级的线程支持,如线程池,已经决定留待在未来的 Technical Report 加入此类支持。更高级的线程支持不会是 C++11 的一部份,但是其最终实现将建立在目前已有的线程支持之上。std::async 提供了一个简便方法来运行线程,并将线程绑定在 std::future上。用户可以选择一个工作是要在多个线程上异步的运行,还是在一个线程上运行并等待其所需要的数据。默认的情况,实现可以根据底层硬件选择前面两个选项的其中之一。另外在较简单的使用场景下,实现也可以利用线程池提供支持。
      11、元组类型
      元组(tuple)由预先确定数量的多种对象组成,元组可以看作是struct数据成员的泛化,在Python中是一个基本数据结构。TR1 tuple类型的C++11版本获益于像可变参数模板这样的C++11语言特性。TR1版本的元组需要一个由实现定义的包含的类型的最大数目,而且需要大量的宏技巧来实现。相比之下,C++11版本的不需要显式的实现定义的最大类型数目。尽管编译器有一个内部的模板实例化的最大递归深度,但C++11版的元组不会把它暴露给用户。
      用可变参数模板,元组类的定义看上去像下面这样:

    template <class ...Types> class tuple;
    //下面是定义和使用元组的一个例子:
    typedef std::tuple <int, double, long &, const char *> test_tuple;
    long lengthy = 12;
    test_tuple proof (18, 6.5, lengthy, "Ciao!");
    lengthy = std::get<0>(proof);  // 把'lengthy' 赋值为18.
    std::get<3>(proof) = " Beautiful!";  // 修改元组的第四个元素

      其它新增标准程序库
      正则化表达式库<regex>;字符串类<string>字新增与其他类型互换的方法,如to_string(),stoi(),stol等;STL标准模板库新增unordered_map以及unordered_set,基于hash表的关联容器等等。这些详细使用可自行上网查找,也十分重要的知识点。

      以上内容参考以下文章总结:

    http://en.wikipedia.org/wiki/C++11
    http://www.cnblogs.com/pzhfei/archive/2013/03/02/CPP_new_feature.html#section_6.3
    http://www.cnblogs.com/haippy/archive/2013/05/31/3111560.html


    个人学习记录,由于能力和时间有限,如果有错误望读者纠正,谢谢!

    转载请注明出处:CSDN 无鞋童鞋。

    展开全文
  • C++11常用新特性快速一览

    万次阅读 多人点赞 2018-02-23 19:28:13
    最近工作中,遇到一些问题,使用C++11实现起来会更加方便,而线上的生产环境还不支持C++11,于是决定新年开工后,在组内把C++11推广开来,整理以下文档,方便自己查阅,也方便同事快速上手。(对于异步编程十分实用...

    最近工作中,遇到一些问题,使用C++11实现起来会更加方便,而线上的生产环境还不支持C++11,于是决定新年开工后,在组内把C++11推广开来,整理以下文档,方便自己查阅,也方便同事快速上手。(对于异步编程十分实用的Future/Promise以及智能指针等,将不做整理介绍,组内使用的框架已经支持并广泛使用了,用的是自己公司参考boost实现的版本)

    1. nullptr

    nullptr 出现的目的是为了替代 NULL。

    在某种意义上来说,传统 C++ 会把 NULL、0 视为同一种东西,这取决于编译器如何定义 NULL,有些编译器会将 NULL 定义为 ((void*)0),有些则会直接将其定义为 0。

    C++ 不允许直接将 void * 隐式转换到其他类型,但如果 NULL 被定义为 ((void*)0),那么当编译char *ch = NULL;时,NULL 只好被定义为 0。

    而这依然会产生问题,将导致了 C++ 中重载特性会发生混乱,考虑:

    void foo(char *);
    void foo(int);

    对于这两个函数来说,如果 NULL 又被定义为了 0 那么 foo(NULL); 这个语句将会去调用 foo(int),从而导致代码违反直观。

    为了解决这个问题,C++11 引入了 nullptr 关键字,专门用来区分空指针、0。

    nullptr 的类型为 nullptr_t,能够隐式的转换为任何指针或成员指针的类型,也能和他们进行相等或者不等的比较。

    当需要使用 NULL 时候,养成直接使用 nullptr的习惯。

    2. 类型推导

    C++11 引入了 auto 和 decltype 这两个关键字实现了类型推导,让编译器来操心变量的类型。

    auto

    auto 在很早以前就已经进入了 C++,但是他始终作为一个存储类型的指示符存在,与 register 并存。在传统 C++ 中,如果一个变量没有声明为 register 变量,将自动被视为一个 auto 变量。而随着 register 被弃用,对 auto 的语义变更也就非常自然了。

    使用 auto 进行类型推导的一个最为常见而且显著的例子就是迭代器。在以前我们需要这样来书写一个迭代器:

    for(vector<int>::const_iterator itr = vec.cbegin(); itr != vec.cend(); ++itr)

    而有了 auto 之后可以:

    // 由于 cbegin() 将返回 vector<int>::const_iterator 
    // 所以 itr 也应该是 vector<int>::const_iterator 类型
    for(auto itr = vec.cbegin(); itr != vec.cend(); ++itr);

    一些其他的常见用法:

    auto i = 5;             // i 被推导为 int
    auto arr = new auto(10) // arr 被推导为 int *

    注意:auto 不能用于函数传参,因此下面的做法是无法通过编译的(考虑重载的问题,我们应该使用模板):

    int add(auto x, auto y);

    此外,auto 还不能用于推导数组类型:

    #include <iostream>
    
    int main() {
     auto i = 5;
    
     int arr[10] = {0};
     auto auto_arr = arr;
     auto auto_arr2[10] = arr;
    
     return 0;
    }

    decltype

    decltype 关键字是为了解决 auto 关键字只能对变量进行类型推导的缺陷而出现的。它的用法和 sizeof 很相似:

    decltype(表达式)

    在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。
    有时候,我们可能需要计算某个表达式的类型,例如:

    auto x = 1;
    auto y = 2;
    decltype(x+y) z;

    拖尾返回类型、auto 与 decltype 配合

    你可能会思考,auto 能不能用于推导函数的返回类型。考虑这样一个例子加法函数的例子,在传统 C++ 中我们必须这么写:

    template<typename R, typename T, typename U>
    R add(T x, U y) {
        return x+y
    }

    这样的代码其实变得很丑陋,因为程序员在使用这个模板函数的时候,必须明确指出返回类型。但事实上我们并不知道 add() 这个函数会做什么样的操作,获得一个什么样的返回类型。

    在 C++11 中这个问题得到解决。虽然你可能马上回反应出来使用 decltype 推导 x+y 的类型,写出这样的代码:

    decltype(x+y) add(T x, U y);

    但事实上这样的写法并不能通过编译。这是因为在编译器读到 decltype(x+y) 时,x 和 y 尚未被定义。为了解决这个问题,C++11 还引入了一个叫做拖尾返回类型(trailing return type),利用 auto 关键字将返回类型后置:

    template<typename T, typename U>
    auto add(T x, U y) -> decltype(x+y) {
        return x+y;
    }

    从 C++14 开始是可以直接让普通函数具备返回值推导,因此下面的写法变得合法:

    template<typename T, typename U>
    auto add(T x, U y) {
        return x+y;
    }

    3. 区间迭代

    基于范围的 for 循环

    C++11 引入了基于范围的迭代写法,我们拥有了能够写出像 Python 一样简洁的循环语句。
    最常用的 std::vector 遍历将从原来的样子:

    std::vector<int> arr(5, 100);
    for(std::vector<int>::iterator i = arr.begin(); i != arr.end(); ++i) {
        std::cout << *i << std::endl;
    }

    变得非常的简单:

    // & 启用了引用
    for(auto &i : arr) {    
        std::cout << i << std::endl;
    }

    4. 初始化列表

    C++11 提供了统一的语法来初始化任意的对象,例如:

    struct A {
        int a;
        float b;
    };
    struct B {
    
        B(int _a, float _b): a(_a), b(_b) {}
    private:
        int a;
        float b;
    };
    
    A a {1, 1.1};    // 统一的初始化语法
    B b {2, 2.2};

    C++11 还把初始化列表的概念绑定到了类型上,并将其称之为 std::initializer_list,允许构造函数或其他函数像参数一样使用初始化列表,这就为类对象的初始化与普通数组和 POD 的初始化方法提供了统一的桥梁,例如:

    #include <initializer_list>
    
    class Magic {
    public:
        Magic(std::initializer_list<int> list) {}
    };
    
    Magic magic = {1,2,3,4,5};
    std::vector<int> v = {1, 2, 3, 4};

    5. 模板增强

    外部模板

    传统 C++ 中,模板只有在使用时才会被编译器实例化。只要在每个编译单元(文件)中编译的代码中遇到了被完整定义的模板,都会实例化。这就产生了重复实例化而导致的编译时间的增加。并且,我们没有办法通知编译器不要触发模板实例化

    C++11 引入了外部模板,扩充了原来的强制编译器在特定位置实例化模板的语法,使得能够显式的告诉编译器何时进行模板的实例化

    template class std::vector<bool>;            // 强行实例化
    extern template class std::vector<double>;  // 不在该编译文件中实例化模板

    尖括号 “>”

    在传统 C++ 的编译器中,>>一律被当做右移运算符来进行处理。但实际上我们很容易就写出了嵌套模板的代码:

    std::vector<std::vector<int>> wow;

    这在传统C++编译器下是不能够被编译的,而 C++11 开始,连续的右尖括号将变得合法,并且能够顺利通过编译。

    类型别名模板

    在传统 C++中,typedef 可以为类型定义一个新的名称,但是却没有办法为模板定义一个新的名称。因为,模板不是类型。例如:

    template< typename T, typename U, int value>
    class SuckType {
    public:
        T a;
        U b;
        SuckType():a(value),b(value){}
    };
    template< typename U>
    typedef SuckType<std::vector<int>, U, 1> NewType; // 不合法

    C++11 使用 using 引入了下面这种形式的写法,并且同时支持对传统 typedef 相同的功效:

    template <typename T>
    using NewType = SuckType<int, T, 1>;    // 合法

    默认模板参数

    我们可能定义了一个加法函数:

    template<typename T, typename U>
    auto add(T x, U y) -> decltype(x+y) {
        return x+y
    }

    但在使用时发现,要使用 add,就必须每次都指定其模板参数的类型。
    在 C++11 中提供了一种便利,可以指定模板的默认参数:

    template<typename T = int, typename U = int>
    auto add(T x, U y) -> decltype(x+y) {
        return x+y;
    }

    6. 构造函数

    委托构造

    C++11 引入了委托构造的概念,这使得构造函数可以在同一个类中一个构造函数调用另一个构造函数,从而达到简化代码的目的:

    class Base {
    public:
        int value1;
        int value2;
        Base() {
            value1 = 1;
        }
        Base(int value) : Base() {  // 委托 Base() 构造函数
            value2 = 2;
        }
    };

    继承构造

    在继承体系中,如果派生类想要使用基类的构造函数,需要在构造函数中显式声明。
    假若基类拥有为数众多的不同版本的构造函数,这样,在派生类中得写很多对应的“透传”构造函数。如下:

    struct A
    {
      A(int i) {}
      A(double d,int i){}
      A(float f,int i,const char* c){}
      //...等等系列的构造函数版本
    };
    struct B:A
    {
      B(int i):A(i){}
      B(double d,int i):A(d,i){}
      B(folat f,int i,const char* c):A(f,i,e){}
      //......等等好多个和基类构造函数对应的构造函数
    };

    C++11的继承构造:

    struct A
    {
      A(int i) {}
      A(double d,int i){}
      A(float f,int i,const char* c){}
      //...等等系列的构造函数版本
    };
    struct B:A
    {
      using A::A;
      //关于基类各构造函数的继承一句话搞定
      //......
    };

    如果一个继承构造函数不被相关的代码使用,编译器不会为之产生真正的函数代码,这样比透传基类各种构造函数更加节省目标代码空间。

    7. Lambda 表达式

    Lambda 表达式,实际上就是提供了一个类似匿名函数的特性,而匿名函数则是在需要一个函数,但是又不想费力去命名一个函数的情况下去使用的。

    Lambda 表达式的基本语法如下:

    [ caputrue ] ( params ) opt -> ret { body; };

    1) capture是捕获列表;
    2) params是参数表;(选填)
    3) opt是函数选项;可以填mutable,exception,attribute(选填)
    mutable说明lambda表达式体内的代码可以修改被捕获的变量,并且可以访问被捕获的对象的non-const方法。
    exception说明lambda表达式是否抛出异常以及何种异常。
    attribute用来声明属性。
    4) ret是返回值类型(拖尾返回类型)。(选填)
    5) body是函数体。

    捕获列表:lambda表达式的捕获列表精细控制了lambda表达式能够访问的外部变量,以及如何访问这些变量。

    1) []不捕获任何变量。
    2) [&]捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。
    3) [=]捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)。注意值捕获的前提是变量可以拷贝,且被捕获的变量在 lambda 表达式被创建时拷贝,而非调用时才拷贝。如果希望lambda表达式在调用时能即时访问外部变量,我们应当使用引用方式捕获。

    int a = 0;
    auto f = [=] { return a; };
    
    a+=1;
    
    cout << f() << endl;       //输出0
    
    int a = 0;
    auto f = [&a] { return a; };
    
    a+=1;
    
    cout << f() <<endl;       //输出1

    4) [=,&foo]按值捕获外部作用域中所有变量,并按引用捕获foo变量。
    5) [bar]按值捕获bar变量,同时不捕获其他变量。
    6) [this]捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样的访问权限。如果已经使用了&或者=,就默认添加此选项。捕获this的目的是可以在lamda中使用当前类的成员函数和成员变量

    class A
    {
     public:
         int i_ = 0;
    
         void func(int x,int y){
             auto x1 = [] { return i_; };                   //error,没有捕获外部变量
             auto x2 = [=] { return i_ + x + y; };          //OK
             auto x3 = [&] { return i_ + x + y; };        //OK
             auto x4 = [this] { return i_; };               //OK
             auto x5 = [this] { return i_ + x + y; };       //error,没有捕获x,y
             auto x6 = [this, x, y] { return i_ + x + y; };     //OK
             auto x7 = [this] { return i_++; };             //OK
    };
    
    int a=0 , b=1;
    auto f1 = [] { return a; };                         //error,没有捕获外部变量    
    auto f2 = [&] { return a++ };                      //OK
    auto f3 = [=] { return a; };                        //OK
    auto f4 = [=] {return a++; };                       //error,a是以复制方式捕获的,无法修改
    auto f5 = [a] { return a+b; };                      //error,没有捕获变量b
    auto f6 = [a, &b] { return a + (b++); };                //OK
    auto f7 = [=, &b] { return a + (b++); };                //OK

    注意f4,虽然按值捕获的变量值均复制一份存储在lambda表达式变量中,修改他们也并不会真正影响到外部,但我们却仍然无法修改它们。如果希望去修改按值捕获的外部变量,需要显示指明lambda表达式为mutable。被mutable修饰的lambda表达式就算没有参数也要写明参数列表

    原因:lambda表达式可以说是就地定义仿函数闭包的“语法糖”。它的捕获列表捕获住的任何外部变量,最终会变为闭包类型的成员变量。按照C++标准,lambda表达式的operator()默认是const的,一个const成员函数是无法修改成员变量的值的。而mutable的作用,就在于取消operator()的const。

    int a = 0;
    auto f1 = [=] { return a++; };                //error
    auto f2 = [=] () mutable { return a++; };       //OK

    lambda表达式的大致原理:每当你定义一个lambda表达式后,编译器会自动生成一个匿名类(这个类重载了()运算符),我们称为闭包类型(closure type)。那么在运行时,这个lambda表达式就会返回一个匿名的闭包实例,是一个右值。所以,我们上面的lambda表达式的结果就是一个个闭包。对于复制传值捕捉方式,类中会相应添加对应类型的非静态数据成员。在运行时,会用复制的值初始化这些成员变量,从而生成闭包。对于引用捕获方式,无论是否标记mutable,都可以在lambda表达式中修改捕获的值。至于闭包类中是否有对应成员,C++标准中给出的答案是:不清楚的,与具体实现有关。

    lambda表达式是不能被赋值的:

    auto a = [] { cout << "A" << endl; };
    auto b = [] { cout << "B" << endl; };
    
    a = b;   // 非法,lambda无法赋值
    auto c = a;   // 合法,生成一个副本

    闭包类型禁用了赋值操作符,但是没有禁用复制构造函数,所以你仍然可以用一个lambda表达式去初始化另外一个lambda表达式而产生副本。

    在多种捕获方式中,最好不要使用[=]和[&]默认捕获所有变量

    默认引用捕获所有变量,你有很大可能会出现悬挂引用(Dangling references),因为引用捕获不会延长引用的变量的生命周期:

    std::function<int(int)> add_x(int x)
    {
        return [&](int a) { return x + a; };
    }

    上面函数返回了一个lambda表达式,参数x仅是一个临时变量,函数add_x调用后就被销毁了,但是返回的lambda表达式却引用了该变量,当调用这个表达式时,引用的是一个垃圾值,会产生没有意义的结果。上面这种情况,使用默认传值方式可以避免悬挂引用问题。

    但是采用默认值捕获所有变量仍然有风险,看下面的例子:

    class Filter
    {
    public:
        Filter(int divisorVal):
            divisor{divisorVal}
        {}
    
        std::function<bool(int)> getFilter() 
        {
            return [=](int value) {return value % divisor == 0; };
        }
    
    private:
        int divisor;
    };

    这个类中有一个成员方法,可以返回一个lambda表达式,这个表达式使用了类的数据成员divisor。而且采用默认值方式捕捉所有变量。你可能认为这个lambda表达式也捕捉了divisor的一份副本,但是实际上并没有。因为数据成员divisor对lambda表达式并不可见,你可以用下面的代码验证:

    // 类的方法,下面无法编译,因为divisor并不在lambda捕捉的范围
    std::function<bool(int)> getFilter() 
    {
        return [divisor](int value) {return value % divisor == 0; };
    }

    原代码中,lambda表达式实际上捕捉的是this指针的副本,所以原来的代码等价于:

    std::function<bool(int)> getFilter() 
    {
        return [this](int value) {return value % this->divisor == 0; };
    }

    尽管还是以值方式捕获,但是捕获的是指针,其实相当于以引用的方式捕获了当前类对象,所以lambda表达式的闭包与一个类对象绑定在一起了,这很危险,因为你仍然有可能在类对象析构后使用这个lambda表达式,那么类似“悬挂引用”的问题也会产生。所以,采用默认值捕捉所有变量仍然是不安全的,主要是由于指针变量的复制,实际上还是按引用传值。

    lambda表达式可以赋值给对应类型的函数指针。但是使用函数指针并不是那么方便。所以STL定义在< functional >头文件提供了一个多态的函数对象封装std::function,其类似于函数指针。它可以绑定任何类函数对象,只要参数与返回类型相同。如下面的返回一个bool且接收两个int的函数包装器:

    std::function<bool(int, int)> wrapper = [](int x, int y) { return x < y; };

    lambda表达式一个更重要的应用是其可以用于函数的参数,通过这种方式可以实现回调函数。

    最常用的是在STL算法中,比如你要统计一个数组中满足特定条件的元素数量,通过lambda表达式给出条件,传递给count_if函数:

    int value = 3;
    vector<int> v {1, 3, 5, 2, 6, 10};
    int count = std::count_if(v.beigin(), v.end(), [value](int x) { return x > value; });

    再比如你想生成斐波那契数列,然后保存在数组中,此时你可以使用generate函数,并辅助lambda表达式:

    vector<int> v(10);
    int a = 0;
    int b = 1;
    std::generate(v.begin(), v.end(), [&a, &b] { int value = b; b = b + a; a = value; return value; });
    // 此时v {1, 1, 2, 3, 5, 8, 13, 21, 34, 55}

    当需要遍历容器并对每个元素进行操作时:

    std::vector<int> v = { 1, 2, 3, 4, 5, 6 };
    int even_count = 0;
    for_each(v.begin(), v.end(), [&even_count](int val){
        if(!(val & 1)){
            ++ even_count;
        }
    });
    std::cout << "The number of even is " << even_count << std::endl;

    大部分STL算法,可以非常灵活地搭配lambda表达式来实现想要的效果。

    8. 新增容器

    std::array

    std::array 保存在栈内存中,相比堆内存中的 std::vector,我们能够灵活的访问这里面的元素,从而获得更高的性能。

    std::array 会在编译时创建一个固定大小的数组,std::array 不能够被隐式的转换成指针,使用 std::array只需指定其类型和大小即可:

    std::array<int, 4> arr= {1,2,3,4};
    
    int len = 4;
    std::array<int, len> arr = {1,2,3,4}; // 非法, 数组大小参数必须是常量表达式

    当我们开始用上了 std::array 时,难免会遇到要将其兼容 C 风格的接口,这里有三种做法:

    void foo(int *p, int len) {
        return;
    }
    
    std::array<int 4> arr = {1,2,3,4};
    
    // C 风格接口传参
    // foo(arr, arr.size());           // 非法, 无法隐式转换
    foo(&arr[0], arr.size());
    foo(arr.data(), arr.size());
    
    // 使用 `std::sort`
    std::sort(arr.begin(), arr.end());

    std::forward_list

    std::forward_list 是一个列表容器,使用方法和 std::list 基本类似。
    和 std::list 的双向链表的实现不同,std::forward_list 使用单向链表进行实现,提供了 O(1) 复杂度的元素插入,不支持快速随机访问(这也是链表的特点),也是标准库容器中唯一一个不提供 size() 方法的容器。当不需要双向迭代时,具有比 std::list 更高的空间利用率。

    无序容器

    C++11 引入了两组无序容器:
    std::unordered_map/std::unordered_multimap 和 std::unordered_set/std::unordered_multiset。

    无序容器中的元素是不进行排序的,内部通过 Hash 表实现,插入和搜索元素的平均复杂度为 O(constant)。

    元组 std::tuple

    元组的使用有三个核心的函数:

    std::make_tuple: 构造元组
    std::get: 获得元组某个位置的值
    std::tie: 元组拆包

    #include <tuple>
    #include <iostream>
    
    auto get_student(int id)
    {
        // 返回类型被推断为 std::tuple<double, char, std::string>
        if (id == 0)
            return std::make_tuple(3.8, 'A', "张三");
        if (id == 1)
            return std::make_tuple(2.9, 'C', "李四");
        if (id == 2)
            return std::make_tuple(1.7, 'D', "王五");
        return std::make_tuple(0.0, 'D', "null");   
        // 如果只写 0 会出现推断错误, 编译失败
    }
    
    int main()
    {
        auto student = get_student(0);
        std::cout << "ID: 0, "
        << "GPA: " << std::get<0>(student) << ", "
        << "成绩: " << std::get<1>(student) << ", "
        << "姓名: " << std::get<2>(student) << '\n';
    
        double gpa;
        char grade;
        std::string name;
    
        // 元组进行拆包
        std::tie(gpa, grade, name) = get_student(1);
        std::cout << "ID: 1, "
        << "GPA: " << gpa << ", "
        << "成绩: " << grade << ", "
        << "姓名: " << name << '\n';
    }

    合并两个元组,可以通过 std::tuple_cat 来实现。

    auto new_tuple = std::tuple_cat(get_student(1), std::move(t));

    9. 正则表达式

    正则表达式描述了一种字符串匹配的模式。一般使用正则表达式主要是实现下面三个需求:
    1) 检查一个串是否包含某种形式的子串;
    2) 将匹配的子串替换;
    3) 从某个串中取出符合条件的子串。

    C++11 提供的正则表达式库操作 std::string 对象,对模式 std::regex (本质是 std::basic_regex)进行初始化,通过 std::regex_match 进行匹配,从而产生 std::smatch (本质是 std::match_results 对象)。

    我们通过一个简单的例子来简单介绍这个库的使用。考虑下面的正则表达式:

    [a-z]+.txt: 在这个正则表达式中, [a-z] 表示匹配一个小写字母, + 可以使前面的表达式匹配多次,因此 [a-z]+ 能够匹配一个及以上小写字母组成的字符串。在正则表达式中一个 . 表示匹配任意字符,而 . 转义后则表示匹配字符 . ,最后的 txt 表示严格匹配 txt 这三个字母。因此这个正则表达式的所要匹配的内容就是文件名为纯小写字母的文本文件。
    std::regex_match 用于匹配字符串和正则表达式,有很多不同的重载形式。最简单的一个形式就是传入std::string 以及一个 std::regex 进行匹配,当匹配成功时,会返回 true,否则返回 false。例如:

    #include <iostream>
    #include <string>
    #include <regex>
    
    int main() {
        std::string fnames[] = {"foo.txt", "bar.txt", "test", "a0.txt", "AAA.txt"};
        // 在 C++ 中 `\` 会被作为字符串内的转义符,为使 `\.` 作为正则表达式传递进去生效,需要对 `\` 进行二次转义,从而有 `\\.`
        std::regex txt_regex("[a-z]+\\.txt");
        for (const auto &fname: fnames)
            std::cout << fname << ": " << std::regex_match(fname, txt_regex) << std::endl;
    }

    另一种常用的形式就是依次传入 std::string/std::smatch/std::regex 三个参数,其中 std::smatch 的本质其实是 std::match_results,在标准库中, std::smatch 被定义为了 std::match_results,也就是一个子串迭代器类型的 match_results。使用 std::smatch 可以方便的对匹配的结果进行获取,例如:

    std::regex base_regex("([a-z]+)\\.txt");
    std::smatch base_match;
    for(const auto &fname: fnames) {
        if (std::regex_match(fname, base_match, base_regex)) {
            // sub_match 的第一个元素匹配整个字符串
            // sub_match 的第二个元素匹配了第一个括号表达式
            if (base_match.size() == 2) {
                std::string base = base_match[1].str();
                std::cout << "sub-match[0]: " << base_match[0].str() << std::endl;
                std::cout << fname << " sub-match[1]: " << base << std::endl;
            }
        }
    }

    以上两个代码段的输出结果为:

    foo.txt: 1
    bar.txt: 1
    test: 0
    a0.txt: 0
    AAA.txt: 0
    sub-match[0]: foo.txt
    foo.txt sub-match[1]: foo
    sub-match[0]: bar.txt
    bar.txt sub-match[1]: bar

    10. 语言级线程支持

    std::thread
    std::mutex/std::unique_lock
    std::future/std::packaged_task
    std::condition_variable

    代码编译需要使用 -pthread 选项

    11. 右值引用和move语义

    先看一个简单的例子直观感受下:

    string a(x);                                    // line 1
    string b(x + y);                                    // line 2
    string c(some_function_returning_a_string());       // line 3

    如果使用以下拷贝构造函数:

    string(const string& that)
    {
        size_t size = strlen(that.data) + 1;
        data = new char[size];
        memcpy(data, that.data, size);
    }

    以上3行中,只有第一行(line 1)的x深度拷贝是有必要的,因为我们可能会在后边用到x,x是一个左值(lvalues)。

    第二行和第三行的参数则是右值,因为表达式产生的string对象是匿名对象,之后没有办法再使用了。

    C++ 11引入了一种新的机制叫做“右值引用”,以便我们通过重载直接使用右值参数。我们所要做的就是写一个以右值引用为参数的构造函数:

    string(string&& that)   // string&& is an rvalue reference to a string
    {
    data = that.data;
    that.data = 0;
    }

    我们没有深度拷贝堆内存中的数据,而是仅仅复制了指针,并把源对象的指针置空。事实上,我们“偷取”了属于源对象的内存数据。由于源对象是一个右值,不会再被使用,因此客户并不会觉察到源对象被改变了。在这里,我们并没有真正的复制,所以我们把这个构造函数叫做“转移构造函数”(move constructor),他的工作就是把资源从一个对象转移到另一个对象,而不是复制他们。

    有了右值引用,再来看看赋值操作符:

    string& operator=(string that)
    {
    std::swap(data, that.data);
    return *this;
    }

    注意到我们是直接对参数that传值,所以that会像其他任何对象一样被初始化,那么确切的说,that是怎样被初始化的呢?对于C++ 98,答案是复制构造函数,但是对于C++ 11,编译器会依据参数是左值还是右值在复制构造函数和转移构造函数间进行选择

    如果是a=b,这样就会调用复制构造函数来初始化that(因为b是左值),赋值操作符会与新创建的对象交换数据,深度拷贝。这就是copy and swap 惯用法的定义:构造一个副本,与副本交换数据,并让副本在作用域内自动销毁。这里也一样。

    如果是a = x + y,这样就会调用转移构造函数来初始化that(因为x+y是右值),所以这里没有深度拷贝,只有高效的数据转移。相对于参数,that依然是一个独立的对象,但是他的构造函数是无用的(trivial),因此堆中的数据没有必要复制,而仅仅是转移。没有必要复制他,因为x+y是右值,再次,从右值指向的对象中转移是没有问题的。

    总结一下:复制构造函数执行的是深度拷贝,因为源对象本身必须不能被改变。而转移构造函数却可以复制指针,把源对象的指针置空,这种形式下,这是安全的,因为用户不可能再使用这个对象了

    下面我们进一步讨论右值引用和move语义。

    C++98标准库中提供了一种唯一拥有性的智能指针std::auto_ptr,该类型在C++11中已被废弃,因为其“复制”行为是危险的。

    auto_ptr<Shape> a(new Triangle);
    auto_ptr<Shape> b(a);

    注意b是怎样使用a进行初始化的,它不复制triangle,而是把triangle的所有权从a传递给了b,也可以说成“a 被转移进了b”或者“triangle被从a转移到了b”。

    auto_ptr 的复制构造函数可能看起来像这样(简化):

    auto_ptr(auto_ptr& source)   // note the missing const
    {
    p = source.p;
    source.p = 0;   // now the source no longer owns the object
    }

    auto_ptr 的危险之处在于看上去应该是复制,但实际上确是转移。调用被转移过的auto_ptr 的成员函数将会导致不可预知的后果。所以你必须非常谨慎的使用auto_ptr ,如果他被转移过。

    auto_ptr<Shape> make_triangle()
    {
        return auto_ptr<Shape>(new Triangle);
    }
    
    auto_ptr<Shape> c(make_triangle());      // move temporary into c
    double area = make_triangle()->area();   // perfectly safe
    
    auto_ptr<Shape> a(new Triangle);    // create triangle
    auto_ptr<Shape> b(a);               // move a into b
    double area = a->area();                // undefined behavior

    显然,在持有auto_ptr 对象的a表达式和持有调用函数返回的auto_ptr值类型的make_triangle()表达式之间一定有一些潜在的区别,每调用一次后者就会创建一个新的auto_ptr对象。这里a 其实就是一个左值(lvalue)的例子,而make_triangle()就是右值(rvalue)的例子。

    转移像a这样的左值是非常危险的,因为我们可能调用a的成员函数,这会导致不可预知的行为。另一方面,转移像make_triangle()这样的右值却是非常安全的,因为复制构造函数之后,我们不能再使用这个临时对象了,因为这个转移后的临时对象会在下一行之前销毁掉。

    我们现在知道转移左值是十分危险的,但是转移右值却是很安全的。如果C++能从语言级别支持区分左值和右值参数,我就可以完全杜绝对左值转移,或者把转移左值在调用的时候暴露出来,以使我们不会不经意的转移左值。

    C++ 11对这个问题的答案是右值引用。右值引用是针对右值的新的引用类型,语法是X&&。以前的老的引用类型X& 现在被称作左值引用。

    使用右值引用X&&作为参数的最有用的函数之一就是转移构造函数X::X(X&& source),它的主要作用是把源对象的本地资源转移给当前对象。

    C++ 11中,std::auto_ptr< T >已经被std::unique_ptr< T >所取代,后者就是利用的右值引用。

    其转移构造函数:

    unique_ptr(unique_ptr&& source)   // note the rvalue reference
    {
        ptr = source.ptr;
        source.ptr = nullptr;
    }

    这个转移构造函数跟auto_ptr中复制构造函数做的事情一样,但是它却只能接受右值作为参数。

    unique_ptr<Shape> a(new Triangle);
    unique_ptr<Shape> b(a);                 // error
    unique_ptr<Shape> c(make_triangle());       // okay

    第二行不能编译通过,因为a是左值,但是参数unique_ptr&& source只能接受右值,这正是我们所需要的,杜绝危险的隐式转移。第三行编译没有问题,因为make_triangle()是右值,转移构造函数会将临时对象的所有权转移给对象c,这正是我们需要的。

    转移左值

    有时候,我们可能想转移左值,也就是说,有时候我们想让编译器把左值当作右值对待,以便能使用转移构造函数,即便这有点不安全。出于这个目的,C++ 11在标准库的头文件< utility >中提供了一个模板函数std::move。实际上,std::move仅仅是简单地将左值转换为右值,它本身并没有转移任何东西。它仅仅是让对象可以转移。

    以下是如何正确的转移左值:

    unique_ptr<Shape> a(new Triangle);
    unique_ptr<Shape> b(a);              // still an error
    unique_ptr<Shape> c(std::move(a));   // okay

    请注意,第三行之后,a不再拥有Triangle对象。不过这没有关系,因为通过明确的写出std::move(a),我们很清楚我们的意图:亲爱的转移构造函数,你可以对a做任何想要做的事情来初始化c;我不再需要a了,对于a,您请自便。

    当然,如果你在使用了mova(a)之后,还继续使用a,那无疑是搬起石头砸自己的脚,还是会导致严重的运行错误。

    总之,std::move(some_lvalue)将左值转换为右值(可以理解为一种类型转换),使接下来的转移成为可能。

    一个例子:

    class Foo
    {
        unique_ptr<Shape> member;
    
    public:
    
        Foo(unique_ptr<Shape>&& parameter)
        : member(parameter)   // error
        {}
    };

    上面的parameter,其类型是一个右值引用,只能说明parameter是指向右值的引用,而parameter本身是个左值。(Things that are declared as rvalue reference can be lvalues or rvalues. The distinguishing criterion is: if it has a name, then it is an lvalue. Otherwise, it is an rvalue.)

    因此以上对parameter的转移是不允许的,需要使用std::move来显示转换成右值。

    展开全文
  • C++:C++11的牛逼特性

    万次阅读 多人点赞 2020-07-02 16:44:19
    一、列表初始化 1.1 C++98中,标准允许使用花括号{}对数组元素进行统一的列表初始值设定。 int array1[] = {1,2,3,4,5}; int array2[] = {0}; 对对于一些自定义类型,却不行. ...C++11扩大了用...

    一、列表初始化
    1.1 C++98中,标准允许使用花括号{}对数组元素进行统一的列表初始值设定。

    int array1[] = {1,2,3,4,5};
    int array2[] = {0};
    

    对对于一些自定义类型,却不行.

    vector<int> v{1,2,3,4,5};
    

    在C++98中这样无法通过编译,因此需要定义vector之后,在使用循环进行初始赋值。
    C++11扩大了用初始化列表的使用范围,让其适用于所有的内置类型和自定义类型,而且使用时,=可以不写

    // 内置类型
    int x1 = {10};
    int x2{10}
    // 数组
    int arr1[5] {1,2,3,4,5}
    int arr2[]{1,2,3,4,5};
    // 标准容器
    vector<int> v{1,2,3}
    map<int,int> m{{1,1},{2,2}}
    // 自定义类型
    class Point
    {
    int x;
    int y;
    }
    Power p{1,2};
    

    1.2 多个对象的列表初始化
    给类(模板类)添加一个带有initializer_list类型参数的构造函数即可支持多个对象的,列表初始化.

    #include<initializer_list>
    template<class T>
    class Vector{
    public:
    	Vecto(initializer_list<T> l)
    		:_capacity(l.size())
    		,_size(0){
    			_array = new T[_capacity];
    			for(auto e : l)
    				_array[_size++] = 3;
    		}
    private;
    	T* _array;
    	size_t _capacity;
    	size_t _size;
    };
    

    二、变量类型推导
    2.1 auto
    在C++中,可以使用auto来根据变量初始化表达式类型推导变量的实际类型,简化程序的书写

    // 不使用auto需要写很长的迭代器的类型
    map<string,string> m;
    map<string,string>::iterator it1 = m.begin();
    // 使用auto就很简单
    auto it2 = m.begin();
    

    2.1 decltype 类型推导
    auto使用时,必须对auto声明的类型进行初始化,否则编译器不能推导出auto的实际类型。
    但是有些场景可能需要根据表达式运行后的结果进行类型推导。因为编译时,代码不会运行,auto也就…

    template<class T1,class T2>
    T1 Add(const T1& a,const T2& b){
    	return a + b;
    }
    

    如果用加完后的结果作为函数的返回类型,可能会出错,这就需要程序运行完后才能知道结果的实际类型,即RTTI(运行时类型识别)
    decltype可以根据表达式的实际类型推演出定义变量时所用的类型

    // 推演表达式作为变量的定义类型
    int a = 1,b=2;
    decltype(a+b) c;
    cout<<typeid(c).name()<<endl;
    
    // 推演函数的返回值类型
    void GetMemory(size_t size){
    	return malloc(size);
    }
    cout<<typeid(decltype(GetMemory)).name()<<endl;
    

    三、基于范围for的循环

    vector<int> v{1,2,3,4,5};
    for(const auto& e : v)
    	cout<<e<<' ';
    cout<<endl;
    

    四、final和override
    在我的多态的文章中有介绍:https://blog.csdn.net/aixintianshideshouhu/article/details/88727084

    五、委派构造函数
    委派构造函数可以通过委派其它构造函数,使多构造函数的类编写更加容易

    class Info
    {
    public;
    	Info()
    		:_type(0)
    		,_name('s')
    		{}
    	Info(int type)
    		:_type(type)
    		,_name('a')
    		{}
    	Info(char a)
    		:_type(0)
    		,_name(a)
    		{}
    pirvate;
    	int _type;
    	char _name;
    };
    

    上面的构造函数除了初始化列表不同之外,其它部分都是类似的,代码重复,可以使用委派构造函数
    委派构造函数就是把构造的任务委派给目标构造函数来完成类的构造

    class Info
    {
    // 目标构造函数
    public:
    	Info()
    		:_type(0)
    		,_a('a')
    	{}
    // 委派构造函数
    	Info(int type)
    		:Info()
    		{
    			_type = type;
    		}
    private;
    	int _type = 0;
    	char _a = 'a';
    };
    

    在初始化列表中调用“基准版本”的构造函数称为委派构造函数,而被调用的“基准版本”则称为目标构造函数

    六、默认函数控制
    在C++中对于空类,编译器会生成一些默认的成员函数,如果在类中显式定义了,编译器就不会重新生成默认版本。但是如果在一个类中声明了带参的构造函数,如果又需要定义不带参的实例化无参的对象。这时候编译器是有时生成,有时不生成,就会造成混乱,C++11可以让程序员自己控制是否需要编译器生成。

    6.1 显式缺省函数
    在C++11中,可以在默认函数定义或声明时加上=default,来让编译器生成该函数的默认版本。

    class A
    {
    public:
    	A(int a)
    		:_a(a)
    	{}
    	A() = default;  // 显式缺省构造函数
    	A& operator=(const A& a);  // 在类中声明,在类外定义时,让编译器生成默认赋值运算符重载
    private:
    	int _a;
    };
    A& A::operator=(const A& a) = default;
    

    6.2 删除默认函数
    要想限制一些默认函数的生成,在C++98中,可以把该函数设为私有,不定义,这样,如果有人调用就会报错。在C++11中,可以给该函数声明加上=delete就可以。

    class A
    {
    A(int a)
    	:_a(a)
    {}
    A(constA&) = delete;  // 禁止编译器生成默认的拷贝构造函数
    private:
    	int _a;
    };
    

    七、右值引用
    7.1 移动语义

    class String
    {
    public:
    	String(char* str = '")
    	{
    		if(str == nullptr)
    			_str = "";
    		_str = new char[strlen(str)+1];
    		strcpy(_str,str);
    	}
    	String(const String& s)
    		:_str(new char[strlen(c._str)+1])
    	{
    		strcpy(_str,s._str);
    	}
    	~String()
    	{
    		if(_str)
    			delete[] _str;
    	}
    private:
    	char* _str;
    };
    
    String GetString(char* pStr)
    {
    	String strTemp(pStr);
    	return strTemp;
    }
    	
    int main()
    {
    	String s1("hello");
    	String s2(GetString("world"));
    	return 0;
    }
    

    在上面的代码中,GetString函数返回的临时对象,将s2拷贝成功之后,立马销毁了(临时对象
    的空间被释放);而s2拷贝构造的时,又需要分配空间,一个刚释放,一个又申请,有点多此一举,那能否把GetString返回的临时对象的空间直接交给s2呢?这样s2也不需要重新开辟空间了。
    在这里插入图片描述
    移动语义:将一个对象资源移动到另一个对象中的方式,在C++中要实现移动语义,必须使用右值引用.

    7.2 C++中的右值
    右值引用,顾名思义就是对右值的引用。在C++中右值由纯右值和将亡值构成。

    • 纯右值:用于识别变量和一些不跟对象关联的值。比如:常量、运算符表达式等、
    • 将亡值:声明周期将要结束的对象。比如:在值返回时的临时对象

    7.3 右值引用
    格式:类型&& 应用变量名字 = 实体;
    使用场景:
    1、与移动语义相结合,减少必须要的资源的开辟,提高运行效率

    String&& GetString(char* pStr)
    {
    	String strTemp(pStr);
    	return strTemp;
    }
    	
    int main()
    {
    	String s1("hello");
    	String s2(GetString("world"));
    	return 0;
    }
    

    2、给一个匿名对象取别名,延长匿名对象的生命周期

    String GetString(char* pStr) {
    return String(pStr);
    }
    int main()
    {
    String&& s = GetString("hello");
    return 0; }
    

    注意

    • 右值引用在定义时必须初始化
    • 右值引用不能引用左值
    int a = 10;
    int&& a1;  // 未初始化,编译失败
    int&& a2 = a;   // 编译失败,a是一个左值
    // 左值是可以改变的值
    

    7.4 std::move()
    C++11中,std::move()函数位于头文件中,它可以把一个左值强制转化为右值引用,通过右值引用使用该值,实现移动语义。该转化不会对左值产生影响.
    注意:其更多用在生命周期即将结束的对象上。

    7.5 移动语义中要注意的问题
    1、在C++11中,无参构造函数/拷贝构造函数/移动构造函数实际上有三个版本

    Object()
    Object(const T&)
    Object(T&&)
    

    2、如果将移动构造函数声明为常右值引用或者返回右值的函数声明为常量,都会导致移动语义无法实现

    String(const String&&);
    const String GetString();
    

    3、C++11默认成员函数,默认情况下,编译器会隐士生成一个移动构造函数,是按照位拷贝来进行。因此在涉及到资源管理时,最好自己定义移动构造函数。

    class String
    {
    public:
    	String(char* str = "")
    	{
    		if(str == nullptr)
    			str = "";
    		_str = new char[strlen(str)+1];
    		strcpy(_str,str);
    	}
    	//  拷贝构造
    	// String s(左值对象)
    	String(const String& s)
    		:_str(new char[strlen(s._str) + 1])
    		{
    			strcpy(_str,s_str);
    		}
    	//  移动构造
    	// String s(将亡值对象)
    	String(String&& s)
    		:_str(nullptr)
    		{
    			swap(_str,s._str);
    		}
    	// 赋值
    	String&  operator=(const String& s)
    	{
    		if(this != &s)
    		{
    			char* tmp = new char[strlen(s._str)+1];
    			stcpy(tmp,s._str);
    			delete[] _str;
    			_str = tmp;
    		}
    		return *this;
    	}
    	// 移动赋值
    	String& operator=(String&& s)
    	{
    		swap(_str,s._str);
    		return *this;
    	}
    	~String()
    	{
    		if(_str)
    			delete[] _str;
    	}
    	// s1 += s2  体现左值引用,传参和传值的位置减少拷贝
    	String& operator+=(const String& s)
    	{
    		// this->Append(s.c_str());
    		return *thisl
    	}
    	// s1 + s2
    	String operator+(const String& s)
    	{
    		String tmp(*this);
    		// tmp.Append(s.c_str());
    		return tmp;
    	}
    	const char* c_str()
    	{
    		return _str;
    	}
    private:
    	char* _str;
    };
    int main()
    {
    	String s1("hello");  // 实例化s1时会调用移动构造
    	String s2("world");
    	String ret 
    	ret = s1 + s2   // +返回的是临时对象,这里会调用移动构造和移动赋值,减少拷贝
    	
    	vector<String> v;
    	String str("world");
    	v.push_back(str);  // 这里调用拷贝构造函数
    	v.push_back(move(str));  // 这里调用移动构造,减少一次拷贝
    	return 0;
    }
    

    在这里插入图片描述
    总结:
    左值:可以改变的值;
    右值: 不可以改变的值(常量,表达式返回值,临时对象)
    左值引用: int& aa = a; 在传参和传值的位置使用,减少拷贝,提高效率
    右值引用: int&& bb = 10; 在传值返回和将亡值传参时,通过调用移动构造和移动赋值,减少拷贝,提高效率。
    const 左值引用可以引用右值
    右值引用可以应用move后的左值

    7.6 完美转发
    完美转发是指在函数模板中,完全按照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数

    void Func(int x)
    {
    	// ......
    }
    template<typename T>
    void PerfectForward(T t)
    {
    	Fun(t);
    }
    

    PerfectForward为完美转发的模板函数,Func为实际目标函数,但上面的转发还不够完美,完美转发是目标函数希望将参数按照传递给转发函数的实际类型转给目标函数,而不产生额外开销,就好像没有转发者一样.
    所谓完美:函数模板在向其他函数传递自身形参时,如果相应实参是左值,就转发左值;如果是右值,就转发右值。(这样是为了保留在其他函数针对转发而来的参数的左右值属性进行不同处理,比如参数为左值时实施拷贝语义、参数为右值时实施移动语义)
    在C++11中,通过forward函数来实现完美转发。

    void Fun(int &x){cout << "lvalue ref" << endl;}
    void Fun(int &&x){cout << "rvalue ref" << endl;}
    void Fun(const int &x){cout << "const lvalue ref" << endl;}
    void Fun(const int &&x){cout << "const rvalue ref" << endl;}
    template<typename T>
    void PerfectForward(T &&t){Fun(std::forward<T>(t));}
    int main()
    {
    PerfectForward(10); // rvalue ref
    int a;
    PerfectForward(a); // lvalue ref
    PerfectForward(std::move(a)); // rvalue ref
    const int b = 8;
    PerfectForward(b); // const lvalue ref
    PerfectForward(std::move(b)); // const rvalue ref
    return 0; }
    

    八、lambda表达式
    在C++98中,如果想对一个数据集合中的元素进行排序,可以使用std::sort()方法,但其默认按照小于比较,如果想排降序,则要传入第三个参数,可以使用std::greater()的比较方法,

    vector<int> v{1,4,3,2,7,6,5};
    // 默认按照小于比较,结果是升序
    sort(v.begin(),v.end());
    // 传入第三个模板参数std::greater<T>(),按照大于比较,默认是降序
    sort(v.begin(), v.end(),greater<int>());
    

    但是该方法只支持内置类型,对于用于自定义的类型就无能为力了,这是就需要用于自定义排序时的规则。目前我们可以通过函数指针,仿函数,lambda来解决。

    1、lambda 表达式语法

    [捕捉列表](参数列表)mutable->返回值类型{函数体}
    捕捉列表:该列表出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表可以捕捉上下文中的变量供lambda函数使用
    参数列表:与普通函数的参数列表一致。则可以连同()一起省略
    mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符,参数列表不可以省略(即使参数列表为空)
    ->返回值类型。用于追踪返回值类型。没有返回值时可以省略。返回值类型明确的情况下,也可以省略
    {函数体}:在该函数体,除了可以使用参数外,也可以使用捕捉到的所有变量
    

    !!!在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。

    int main()
    {
    	// 最简单的lambda表达式
    	[]{};
    	// 省略参数列表和返回值类型,返回值类型有编译器推演为int
    	int a=3,b=4;
    	[=]{return a+3;};
    	// 省略返回值类型
    	auto fun1 = [&](int c){b = a + c;};
    	// 各部分完整的lambda函数
    	auto fun2 = [=,&b](int c)->int(return += a + c;);
    	// 复制捕捉x
    	int x = 10;
    	auto add_x = [x](int a)mutable{x *= 2; return a + x;};
    	return 0;
    }
    
    

    2、捕获列表说明
    捕获列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是引用

    a [var]:表示值传递方式捕获变量var
    b [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
    c [&var]:表示引用传递变量var
    d [&]:表示引用传递捕获所有父作用域中的变量(this)
    e [this]:表示值传递方式捕获当前的this指针
    

    !!!:

    a 父作用域包含lambda函数的语句块
    b 语法上捕获列表可由多个捕获项组成,并以逗号分隔
    	比如:[=,&a,&b]:以引用传递的方式捕获变量a 和 b,值传递的方式捕获其它所有变量.
    	[&,a,this];值传递的方式捕获变量a和this,引用方式捕获其它变量。
    	捕捉列表不允许变量重复传递,否则会导致编译错误。比如:[=,a]以传值的方式捕获了所有变量,又重复捕捉a
    c 块作用域以外的lambda函数捕捉列表必须为空
    e 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错
    f lambda表达式之间不能相互赋值,即使看起来类型相同.
    
    void (*PF)();
    int main()
    {
    	auto f1 = []{cout<<"hello world"<<endl;};
    	auto f2 = []{cout<<"hello world"<<endl;};
    	f1= f2; // 这里会编译失败,提示找不到operator=()
    	auto f3(f2);  // 允许使用一个lambda表达式拷贝一个新的福分
    	PF = f2; // 可以将lambda表达式赋值给相同类型的指针
    	return 0;
    }
    

    3、lambda表达式与函数指针、仿函数

    typedef bool (*GTF) (int, int);
    bool greater_func1(int l, int r)
    {
    	return l > r;
    }
    
    struct greater_func2
    {
    	bool operator()(int l, int r)
    	{
    		return l > r;
    	}
    };
    
    int main()
    {
    	// 函数指针
    	GTF f1 = greater_func1;  // typedef 定义
        // bool (*f1) (int, int) = greater_func1;  // 不typedef ,直接原生写法,可读性差
        cout<< f1(1,2)<<endl;
        // 仿函数
        greater_func2 f2;
        cout<< f2(1,2)<<endl;
       // lamba表达式
       auto f3 = [] (int l, int r) ->bool{return l > r;};
       cout<< f3(1,2)<<endl;
        
    	int a[] = {1,2,4,5,3,7,6,9,8};
    	sort(a,a+sizeof(a)/sizeof(a[0]),f1);
    	sort(a,a+sizeof(a)/sizeof(a[0]),f2);
    	sort(a,a+sizeof(a)/sizeof(a[0]),f3);
    	// sort函数第三个模板参数能接受函数指针,仿函数、lambda表达式,是因为其第三个参数是一个模板custom (2)	
    	template <class RandomAccessIterator, class Compare>
      void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);
      
    	return 0;
    }
    

    函数指针,仿函数,lambda用法上是一样的,但函数指针类型定义很难理解,仿函数需要实现运算符的重载,必须先定义一个类,而且一个类只能实现一个()operator的重载。(ep:对商品的不同属性实现比较就需要实现不同的类),要先定义好才能使用。而lambda可以定义好直接使用.

    struct Goods
    {
    	string _name;
    	double _price;
    	double _appraise;
    };
    
    int main()
    {
    	Goods gds[] = { { "苹果", 2.1, 10 }, { "相交", 3, 8 }, { "橙子", 2.2, 7 }, { "菠萝", 1.5, 10 } };
    
    	sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& g1, const Goods& g2)->bool
    	{return g1._price > g2._price; });
    
    	sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& g1, const Goods& g2)->bool
    	{return g1._appraise > g2._appraise; });
    
    	return 0;
    }
    

    上面的例子就体现了其现做现用的特性。

    4、lambda表达式的底层

    class Rate
    {
    public:
    Rate(double rate)
     : _rate(rate)
     {}
    double operator()(double money, int year)
     {
    return money * _rate * year;
     }
    private:
    double _rate;
    };
    int main()
    {
    // 函数对象
    double rate = 0.49;
    Rate r1(rate);
    r1(10000, 2);
    // 仿函数
    auto r2 = [=](double monty, int year)->double{return monty*rate*year; };
    r2(10000, 2);
    return 0; }
    

    函数对象将rate作为其成员变量,在定义对象时候给出初始值即可,lambda表达式通过捕获列表直接捕获该变量.
    在这里插入图片描述
    在这里插入图片描述
    通过上面的图可以看出,实际在底层编译器对于处理lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator();
    并且编译器是通过lambda_+uuid来唯一辨识一个lambda表达式的
    九、线程库
    C++11中引入了线程库,使得在C++在并行编程时可以不需要依赖第三方库,而且在原子操作中引入了原子类的概念。
    要使用标准库中的线程,必须包含头文件

    #include<iostream>
    #include<thread>
    
    void fun()
    {
    	std::cout << "A new thread!" << std::endl;
    }
    int main()
    {
    	std::thread t(fun);
    	t.join();
    	std::cout << "Main thread!" << std::endl;
    	system("pause");
    	return 0;
    }
    

    在这里插入图片描述
    9.1 线程的启动
    C++线程库通过构造一个线程对象来启动一个线程,该线程对象中包含了线程运行时的上下文环境,如:线程函数、线程栈、线程其实状态、以及线程ID等,把所有操作全部封装在一起,在同一传递给_beginthreadex()创建线程函数来实现(_beginthreadex是windows中创建线程的底层c函数)
    std::thread()创建一个新的线程可以接受任意的可调用对象类型,包括lambda表达式,函数,函数对象,函数指针

    // 使用lambda表达式作为线程函数创建线程
    int main()
    {
    	int n1 = 1;
    	int n2 = 2;
    	std::thread t([&](int addNum){n1 += addNum; n2 += addNum; }, 3);
    	t.join();
    	std::cout << n1 << " " << n2 << std:: endl;
    	system("pause");
    	return 0;
    }
    

    9.1 线程的结束
    启动一个线程后,当线程执行完毕时,如果护手线程使用的资源,thread库提供了两种选择。
    1、join()
    join():会主动等待线程终止,在调用进程中join(),当新的线程终止时,join()会清理相关的资源,然后返回,调用线程在继续向下执行。由于join()清理了线程的相关资源,thread对象与已销毁的线程就没有关系了,因此一个线程对象每次只能join()一次,如果多次调用join(),joinable()会返回false;

    int main()
    {
    	int n1 = 1;
    	int n2 = 2;
    	std::thread t([&](int addNum){n1 += addNum; n2 += addNum; }, 3);
    	std::cout << "join before,joinable=" << t.joinable() << std::endl;
    	t.join();
    	std::cout << "join after,joinable=" << t.joinable() << std::endl;
    	system("pause");
    	return 0;
    }
    // 执行结果:
    join before,joinable=1
    join after,joinable=0
    

    2、detach()
    detach:会从调用线程中分离出新的线程,之后不能再与新线程交互。这是调用joinable()会返回false。分离的线程会在后台运行,其所有权和控制权会交给C++运行库。C++运行库会保证在线程退出时,其相关资源能正确回收。

    int main()
    {
    	int n1 = 1;
    	int n2 = 2;
    	std::thread t([&](int addNum){n1 += addNum; n2 += addNum; }, 3);
    	std::cout << "join before,joinable=" << t.joinable() << std::endl;
    	t.detach();
    	std::cout << "join after,joinable=" << t.joinable() << std::endl;
    	system("pause");
    	return 0;
    }
    

    注意,必须在thread对象销毁之前作出选择,因为线程在join()或detach()之前,就可能已经结束,如果之后在分离,线程可能会在thread对象销毁之后继续运行。

    9.3 原子性操作库
    多线程最主要的问题是共享数据带来的问题(线程安全)。如果数据都是只读的,没有问题,因为只读不会影响数据,不会涉及数据的修改,所有线程都会获得同样的数据。但是,当多个线程要修改数据时,就会产生很多潜在的麻烦。

    int sum = 0;
    
    void fun(size_t num)
    {
    	for (size_t i = 0; i < num; i++)
    		sum++;
    }
    
    int main()
    {
    	std::cout << "before,sum=" << sum << std::endl;
    	std::thread t1(fun, 100000000);
    	std::thread t2(fun, 100000000);
    	t1.join();
    	t2.join();
    	std::cout << "After,sum=" << sum << std::endl;
    	system("pause");
    	return 0;
    }
    

    当fun的参数比较大时,就会产生和预期不相符的结果.
    在C++98中可以通过加锁来保护共享数据。

    int sum = 0;
    std::mutex m;
    
    void fun(size_t num)
    {
    	for (size_t i = 0; i < num; i++)
    	{
    		m.lock();
    		sum++;
    		m.unlock();
    	}
    }
    

    虽然加锁结果了这个问题:但是它有一个缺陷:只要有一个线程在对sum++的时候,其它线程就会阻塞,会影响程序运行的效率,而且锁如果控制不好,或导致思索的问题。
    因此在C++11中引入了原子操作。对应于内置的数据类型,原子数据类型都有一份对应的类型。
    在这里插入图片描述
    要使用以上的原子操作,需要添加头文件

    #include<thread>
    #include<mutex>
    #include<atomic>
    
    std::atomic_int sum{ 0 };
    
    void fun(size_t num)
    {
    	for (size_t i = 0; i < num; i++)
    	{
    		sum ++; // 原子的
    	}
    }
    
    int main()
    {
    	std::cout << "before,sum=" << sum << std::endl;
    	std::thread t1(fun, 10000000);
    	std::thread t2(fun, 10000000);
    	t1.join();
    	t2.join();
    	std::cout << "After,sum=" << sum << std::endl;
    	system("pause");
    	return 0;
    }
    
    展开全文
  • C++ 11常用特性

    千次阅读 2019-05-16 16:40:28
     auto并没有让C++成为弱类型语言,也没有弱化变量什么,只是使用auto的时候,编译器根据上下文情况,确定auto变量的真正类型。 #include "stdafx.h" #include <string> #include <list> #include <...

     

    auto关键字及用法

    A、auto关键字能做什么?

      auto并没有让C++成为弱类型语言,也没有弱化变量什么,只是使用auto的时候,编译器根据上下文情况,确定auto变量的真正类型。

    #include "stdafx.h"
    #include <string>
    #include <list>
    #include <iostream>
    
    using namespace std;
    
    auto addTest(int a, int b) {
    	return a + b;
    }
    int main()
    {
    	
    	auto index = 10;
    	auto str = "str";
    	auto ret = addTest(1, 2);
    	cout << index << str << ret << endl;
    
    	system("pause");
    	return 0;
    
    

    B、auto不能做什么?

      auto作为函数返回值时,只能用于定义函数,不能用于声明函数

    #include "stdafx.h"
    #include <string>
    #include <list>
    #include <iostream>
    
    using namespace std;
    
    
    class Test {
    public:
    	auto add(int a, int b);
    //	{
    	//	return a + b;
    //	}
    };
    int main()
    {
    
    	Test a;
    	a.add(3, 4);
    	system("pause");
    	return 0;
    }
    
    

    但如果把实现写在头文件中,可以编译通过,因为编译器可以根据函数实现的返回值确定auto的真实类型。

    #include "stdafx.h"
    #include <string>
    #include <list>
    #include <iostream>
    
    using namespace std;
    
    
    class Test {
    public:
    	auto add(int a, int b)
    	{
    		return a + b;
    	}
    };
    int main()
    {
    
    	Test a;
    	a.add(3, 4);
    	system("pause");
    	return 0;
    }

     

    nullptr关键字及用法

    nullptr是为了解决原来C++中NULL的二义性问题而引进的一种新的类型,由于NULL实际上代表的是0,

    void F(int a){
        cout<<a<<endl;
    }
    
    void F(int *p){
        assert(p != NULL);
    
        cout<< p <<endl;
    }
    
    int main(){
    
        int *p = nullptr;
        int *q = NULL;
        bool equal = ( p == q ); // equal的值为true,说明p和q都是空指针
        int a = nullptr; // 编译失败,nullptr不能转型为int
        F(0); // 在C++98中编译失败。有二义性。在C++11中调用F(int)
        F(nullptr);
    
        return 0;
    }

    for循环语法

    #include "stdafx.h"
    #include <string>
    #include <list>
    #include <iostream>
    #include <map>
    using namespace std;
    int main()
    {
    	int numbers[] = {1,2,3,4,5,6};
    	for (auto number : numbers)
    		cout << number << endl;
    
    
    	map<string, int>m{ {"a",1},{"b",2},{"c",3} };
    	for (auto p : m)
    		cout << p.first << ":" << p.second << endl;
    
    	system("pause");
    	return 0;
    }

     

    long long 类型

    扩展精度浮点数,10位有效数字

     

    列表初始化

    花括号初始化器列表

    int static_arr[5] = { 1, 2, 3, 4 };
    int static_arr2[]{ 1, 2, 3, 4 }; // 等号要以省略
    int* dynamic_arr = new int[5]{ 1, 2, 3, 4 };
    vector<int> stl_vec{ 1, 2, 3, 4 };
    set<int> stl_set{ 1, 2, 3, 3 };

     

    constexpr 变量

    将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式;

    声明为constexpr的变量一定是一个常量,而且必须用常量表达式来初始化,比如说下面的情况则是不正确的:

    int t = 10;
    constexpr int q = t + 20;
    cout << "q" << q << endl;
    

    需要将t声明为 const 才是正确的;

    一般来说,如果你认定变量是一个常量表达式,那就把它声明为constexpr类型;

     

    std::array

    std::array除了有传统数组支持随机访问、效率高、存储大小固定等特点外,还支持迭代器访问、获取容量、获得原始指针等高级功能.

    #include "stdafx.h"
    #include <string>
    #include <list>
    #include <iostream>
    #include <map>
    #include <array>
    using namespace std;
    int main()
    {
    	array<int, 4> arrayDemo = {5,6,7,8};
    	for (auto itor : arrayDemo)
    		cout << itor << endl;
    
    	int arraySize = sizeof(arrayDemo);
    	cout << arraySize << endl;
    
    	system("pause");
    	return 0;
    }

    std::forward_list

    td::forward_list为C++ 11新增的线性表,与list区别在于它是单向链表。我们在学习数据结构的时候都知道,链表在对数据进行插入和删除是比顺序存储的线性表有优势,因此在插入和删除操作频繁的应用场景中,使用list和forward_list比使用array、vector和deque效率要高很多。

    #include <forward_list>
    
    using namespace std;
    int main()
    {
    	forward_list<int> numbers = {1,2,3,4,5};
    	for (auto number : numbers)
    		cout << number << endl;
    
    	numbers.remove(4);
    	for (auto number : numbers)
    		cout << number << endl;
    
    	system("pause");
    	return 0;
    }

    std::unordered_map

    std::unordered_map与std::map用法基本差不多,但STL在内部实现上有很大不同,std::map使用的数据结构为二叉树,而std::unordered_map内部是哈希表的实现方式,哈希map理论上查找效率为O(1)。但在存储效率上,哈希map需要增加哈希表的内存开销。

    std::unordered_set

    std::unordered_set的数据存储结构也是哈希表的方式结构,除此之外,std::unordered_set在插入时不会自动排序,这都是std::set表现不同的地方。

    #include <list>
    #include <iostream>
    #include <map>
    #include <array>
    #include <forward_list>
    #include <unordered_set>
    #include <set>
    using namespace std;
    int main()
    {
    	unordered_set<int> unorder_set;
    	unorder_set.insert(7);
    	unorder_set.insert(5);
    	unorder_set.insert(3);
    	unorder_set.insert(4);
    	unorder_set.insert(1);
    	for (auto itor : unorder_set)
    		cout << itor << endl;
    
    	set<int> set;
    	set.insert(7);
    	set.insert(5);
    	set.insert(3);
    	set.insert(4);
    	set.insert(1);
    	for (auto itor : set)
    		cout << itor << endl;
    
    
    	system("pause");
    	return 0;
    }

    cbegin和cend

    begin():Return iterator to beginning (public member function )
    cbegin():Return const_iterator to beginning (public member function )

    后者返回的是一个不可修改的迭代器,前者可修改。


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

      我们通过如下几部分介绍C++11多线程方面的接口及使用方法。

    std::thread

    std::thread为C++11的线程类,使用方法和boost接口一样,非常方便,同时,C++11的std::thread解决了boost::thread中构成参数限制的问题.

    #include <thread>
    
    using namespace std;
    void fun(int a) {
    	a++;
    	cout << a << endl;
    }
    
    int main()
    {
    	int a = 0;
    	thread t(fun, a);//创建一个线程 t,t  调用函数fun,a作为fun的参数 
    	t.join();//启动线程t,并阻塞主线程,等到线程t运行结束后,再继续运行主线程
    
    	system("pause");
    	return 0;
    }

                thread类当中的两个成员函数,join()和detach()。这两个成员的作用就像上面代码的注释那样,启动新生成的线程的,但是区别在于join()函数是启动子线程而阻塞主线程,当子线程运行结束后,才会继续运行主线程。相比之下,detach()函数的作用是启动子线程,并且让子线程和主线程分离,子线程和主线程各运行各的,虽然两个线程会因为共享内存池的原因在操作系统的层面发生发生阻塞等关系,但是在代码层次上,两个线程并不存在谁阻塞谁,很可能主线程已经运行结束了,子线程还在运行。

    std::atomic

    所谓的原子操作,取的就是“原子是最小的、不可分割的最小个体”的意义,它表示在多个线程访问同一个全局资源的时候,能够确保所有其他的线程都不在同一时间内访问相同的资源。也就是他确保了在同一时刻只有唯一的线程对这个资源进行访问。这有点类似互斥对象对共享资源的访问的保护,但是原子操作更加接近底层,因而效率更高。
    在以往的C++标准中并没有对原子操作进行规定,我们往往是使用汇编语言,或者是借助第三方的线程库,例如intel的pthread来实现。在新标准C++11,引入了原子操作的概念,并通过这个新的头文件提供了多种原子操作数据类型,例如,atomic_bool,atomic_int等等,如果我们在多个线程中对这些类型的共享资源进行操作,编译器将保证这些操作都是原子性的,也就是说,确保任意时刻只有一个线程对这个资源进行访问,编译器将保证,多个线程访问这个共享资源的正确性。从而避免了锁的使用,提高了效率。
    std::atomic对int, char, bool等数据结构进行原子性封装,在多线程环境中,对std::atomic对象的访问不会造成竞争-冒险。利用std::atomic可实现数据结构的无锁设计

     

    #include <thread>
    #include <atomic>
    using namespace std;
    
    atomic_bool bIsReady = false;
    atomic_int iCount = 100;
    void fun() {
    	if (!bIsReady) {
    		this_thread::yield();
    	}
    	while (iCount > 0)
    	{
    		printf("iCount:%d\r\n", iCount--);
    	}
    }
    
    int main()
    {
    	list<thread> lstThread;
    	for (int i = 0; i < 10; ++i) {
    		lstThread.push_back(thread(fun));
    	}
    
    	for (auto &th : lstThread)
    	{
    		th.join();
    	}
    
    	system("pause");
    	return 0;
    }

    std::condition_variable

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

    #include <set>
    #include <thread>
    #include <atomic>
    #include <condition_variable>
    #include <mutex>
    using namespace std;
    
    mutex mtx;
    condition_variable cv;
    bool ready = false;
    
    void print_id(int id) {
    	unique_lock<mutex> lck(mtx);
    	while (!ready)
    		cv.wait(lck);
    
    		cout << "thread" << id << endl;
    }
    
    void go() {
    	unique_lock<mutex> lck(mtx);
    	ready = true;
    	cv.notify_all();
    }
    
    
    int main()
    {
    	thread threads[10];
    	for (int i = 0; i < 10; ++i)
    		threads[i] = thread(print_id, i);
    	cout << "10 threads ready to race..." << endl;
    
    	go();
    
    	for (auto &th : threads)
    		th.join();
    
    	system("pause");
    	return 0;
    }

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


    在内存管理方面,C++11的std::auto_ptr基础上,移植了boost库中的智能指针的部分实现,如std::shared_ptr、std::weak_ptr等,当然,想boost::thread一样,C++11也修复了boost::make_shared中构造参数的限制问题。

      什么是智能指针?网上已经有很多解释,个人觉得“智能指针”这个名词似乎起得过于“霸气”,很多初学者看到这个名词就觉得似乎很难。

      简单地说,智能指针只是用对象去管理一个资源指针,同时用一个计数器计算当前指针引用对象的个数,当管理指针的对象增加或减少时,计数器也相应加1或减1,当最后一个指针管理对象销毁时,计数器为1,此时在销毁指针管理对象的同时,也把指针管理对象所管理的指针进行delete操作。

      如下图所示,简单话了一下指针、智能指针对象和计数器之间的关系:

      

    std::shared_ptr

    std::shared_ptr包装了new操作符动态分别的内存,可以自由拷贝复制,基本上是使用最多的一个智能指针类型

    // ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
    //
    
    #include "stdafx.h"
    #include <string>
    #include <memory>
    #include <iostream>
    
    using namespace std;
    
    class Test {
    public:
    	Test()
    	{
    		cout << "Test()" << endl;
    	}
    	~Test()
    	{
    		cout << "~Test()" << endl;
    	}
    };
    
    int main()
    {
    	shared_ptr<Test> p1 = make_shared<Test>();
    	cout << "1 ref" << p1.use_count() << endl;
    
    	shared_ptr<Test> p2 = p1;
    	cout << "2 ref" << p1.use_count() << endl;
    
    	cout << "3 ref" << p1.use_count() << endl;
    
    	system("pause");
    	return 0;
    }
    
    

    1、std::make_shared封装了new方法,boost::make_shared之前的原则是既然释放资源delete由智能指针负责,那么应该把new封装起来,否则会让人觉得自己调用了new,但没有调用delete,似乎与谁申请,谁释放的原则不符。C++也沿用了这一做法。

    2、随着引用对象的增加std::shared_ptr<Test> p2 = p1,指针的引用计数有1变为2,当p2退出作用域后,p1的引用计数变回1,当main函数退出后,p1离开main函数的作用域,此时p1被销毁,当p1销毁时,检测到引用计数已经为1,就会在p1的析构函数中调用delete之前std::make_shared创建的指针。

    std::weak_ptr

    std::weak_ptr网上很多人说其实是为了解决std::shared_ptr在相互引用的情况下出现的问题而存在的,C++官网对这个只能指针的解释也不多,那就先甭管那么多了,让我们暂时完全接受这个观点。

      std::weak_ptr有什么特点呢?与std::shared_ptr最大的差别是在赋值是,不会引起智能指针计数增加。

      我们下面将继续如下两点:

      1、std::shared_ptr相互引用会有什么后果;

      2、std::weak_ptr如何解决第一点的问题。

      A、std::shared_ptr相互引用的问题示例:

    //示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
    #include <memory>
    class TestB;
    class TestA
    {
    public:
        TestA()
        {
            std::cout << "TestA()" << std::endl;
        }
        void ReferTestB(std::shared_ptr<TestB> test_ptr)
        {
            m_TestB_Ptr = test_ptr;
        }
        ~TestA()
        {
            std::cout << "~TestA()" << std::endl;
        }
    private:
        std::shared_ptr<TestB> m_TestB_Ptr; //TestB的智能指针
    }; 
    
    class TestB
    {
    public:
        TestB()
        {
            std::cout << "TestB()" << std::endl;
        }
    
        void ReferTestB(std::shared_ptr<TestA> test_ptr)
        {
            m_TestA_Ptr = test_ptr;
        }
        ~TestB()
        {
            std::cout << "~TestB()" << std::endl;
        }
        std::shared_ptr<TestA> m_TestA_Ptr; //TestA的智能指针
    };
    
    
    int main()
    {
        std::shared_ptr<TestA> ptr_a = std::make_shared<TestA>();
        std::shared_ptr<TestB> ptr_b = std::make_shared<TestB>();
        ptr_a->ReferTestB(ptr_b);
        ptr_b->ReferTestB(ptr_a);
        return 0;
    }

    运行结果:

        

      大家可以看到,上面代码中,我们创建了一个TestA和一个TestB的对象,但在整个main函数都运行完后,都没看到两个对象被析构,这是什么问题呢?

      原来,智能指针ptr_a中引用了ptr_b,同样ptr_b中也引用了ptr_a,在main函数退出前,ptr_a和ptr_b的引用计数均为2,退出main函数后,引用计数均变为1,也就是相互引用。

      这等效于说:

        ptr_a对ptr_b说,哎,我说ptr_b,我现在的条件是,你先释放我,我才能释放你,这是天生的,造物者决定的,改不了。

        ptr_b也对ptr_a说,我的条件也是一样,你先释放我,我才能释放你,怎么办?

      是吧,大家都没错,相互引用导致的问题就是释放条件的冲突,最终也可能导致内存泄漏。

     

      B、std::weak_ptr如何解决相互引用的问题

      我们在上面的代码基础上使用std::weak_ptr进行修改:

    //示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
    #include <memory>
    class TestB;
    class TestA
    {
    public:
        TestA()
        {
            std::cout << "TestA()" << std::endl;
        }
        void ReferTestB(std::shared_ptr<TestB> test_ptr)
        {
            m_TestB_Ptr = test_ptr;
        }
        void TestWork()
        {
            std::cout << "~TestA::TestWork()" << std::endl;
        }
        ~TestA()
        {
            std::cout << "~TestA()" << std::endl;
        }
    private:
        std::weak_ptr<TestB> m_TestB_Ptr;
    };
    
    class TestB
    {
    public:
        TestB()
        {
            std::cout << "TestB()" << std::endl;
        }
    
        void ReferTestB(std::shared_ptr<TestA> test_ptr)
        {
            m_TestA_Ptr = test_ptr;
        }
        void TestWork()
        {
            std::cout << "~TestB::TestWork()" << std::endl;
        }
        ~TestB()
        {
            std::shared_ptr<TestA> tmp = m_TestA_Ptr.lock();
            tmp->TestWork();
            std::cout << "2 ref a:" << tmp.use_count() << std::endl;
            std::cout << "~TestB()" << std::endl;
        }
        std::weak_ptr<TestA> m_TestA_Ptr;
    };
    
    
    int main()
    {
        std::shared_ptr<TestA> ptr_a = std::make_shared<TestA>();
        std::shared_ptr<TestB> ptr_b = std::make_shared<TestB>();
        ptr_a->ReferTestB(ptr_b);
        ptr_b->ReferTestB(ptr_a);
        std::cout << "1 ref a:" << ptr_a.use_count() << std::endl;
        std::cout << "1 ref b:" << ptr_a.use_count() << std::endl;
        return 0;
    }

    运行结果:

          

      由以上代码运行结果我们可以看到:

      1、所有的对象最后都能正常释放,不会存在上一个例子中的内存没有释放的问题。

      2、ptr_a 和ptr_b在main函数中退出前,引用计数均为1,也就是说,在TestA和TestB中对std::weak_ptr的相互引用,不会导致计数的增加。在TestB析构函数中,调用std::shared_ptr<TestA> tmp = m_TestA_Ptr.lock(),把std::weak_ptr类型转换成std::shared_ptr类型,然后对TestA对象进行调用。


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

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

      此处省略很多字。。。。。。

    lamda表达式

    形式:

    [capture](parameters) mutable ->return-type{statement}

    1.[var]表示值传递方式捕捉变量var;
    2.[=]表示值传递方式捕捉所有父作用域的变量(包括this);
    3.[&var]表示引用传递捕捉变量var;
    4.[&]表示引用传递方式捕捉所有父作用域的变量(包括this);
    5.[this]表示值传递方式捕捉当前的this指针。

     

    如:

    #include "stdafx.h"
    #include <string>
    #include <memory>
    #include <iostream>
    #include <functional>
    using namespace std;
    
    
    int main()
    {
    	auto add = [](int a, int b)->int {
    		return a + b;
    	};
    
    	int ret = add(1, 2);
    	cout << "ret" << ret << endl;
    	return 0;
    }
    
    

    如:

    #include <iostream>  
      
    using namespace std;  
      
    int main()  
    {  
        auto func = [] () { cout << "Hello world"; };  
        func(); // now call the function  
    }  

    如: 

    //Qt5 Lambda表达式
    //这里需要注意 Lambda表达式是C++ 11 的内容,所以,需要再Pro项目文件中加入 CONFIG += C++ 11 
    
    QObject::connect(ui->pushButton_3,&QPushButton::clicked,[=](){qDebug()<<"lambda 表达式";});

     

    参考:https://www.cnblogs.com/mengfanrong/p/5230558.html

    https://www.cnblogs.com/feng-sc/p/5710724.html

    https://blog.csdn.net/yhc166188/article/details/80572108

    https://www.cnblogs.com/yuankaituo/p/5135750.html

     

     

     

    展开全文
  • 深入理解C++11

    万次阅读 2020-02-29 18:10:17
    第2章 保证稳定性和兼容性 finaloverride 控制 局部和匿名类型作模板实参 第3章 通用为本专用为末 继承构造函数 委派构造函数 右值引用移动语义和完美转发 移动语义 右值和左值的区别 stdmove 强制转化为右值 ...
  • C++11常用新特性汇总

    千次阅读 2018-12-17 14:53:18
    C++11已经出来很久了,网上也早有很多优秀的C++11新特性的总结文章,在编写本博客之前,博主在工作和学习中学到的关于C++11方面的知识,也得益于很多其他网友的总结。本博客文章是在学习的基础上,加上博主在日常...
  • c++11/14/17标准你了解多少

    万次阅读 2018-09-09 23:08:54
    本文就带你一探c++11新标准。官网链接:https://msdn.microsoft.com/zh-cn/library/hh567368.aspx#featurelist C++11 功能列表 C++11 核心语言功能表 C++11 核心语言功能表:并发 C++11 ...
  • 征服C++ 11视频精讲

    万人学习 2020-08-04 18:23:28
    李宁老师结合4大国外顶级C++著作的精华为大家推出的《征服C++11》课程。 【学完后我将达到什么水平?】 1.对C++的各个知识能够熟练配置、开发、部署; 2.吊打一切关于C++的笔试面试题; ...
  • C++11多线程编程

    千人学习 2020-04-28 10:01:41
    本课程,讲解的重点定位在c++11新标准中的多线程开发部分,同时,老师还会结合自己的经验把多线程的讲解进一步拓展到一个比较大的范畴,因为无论是c++11多线程开发还是各种其他的多线程开发实现方法,都有很多类似的...
  • Dev C++添加C++11标准

    千次阅读 2019-04-12 19:53:36
    Dev C++添加C++11标准 首先确保Dev C++版本是最新的5.11版 不用C++11标准的语法去运行大多数时候会warning,但是,有时候可能会报错,比如说,不能使用stoll函数,Lambda表达式等等 可以点击菜单栏的“工具”-》...
  • C++11较C++03的改进

    千次阅读 多人点赞 2014-04-29 21:14:55
    C++11又称为C++0x是新的C++语言的标准,发布于2011年。新的标准取代了2003年发布的C++03,也为C++带来了些许改进。
  • C++11新特性学习

    万次阅读 多人点赞 2018-11-27 15:18:28
    C++11标准为C++编程语言的第三个官方标准,正式名叫ISO/IEC 14882:2011 - Information technology -- Programming languages -- C++。在正式标准发布前,原名C++0x。它将取代C++标准第二版ISO/IEC 14882:2003 - ...
  • C++ 编译器对C++11、14、17的支持情况

    万次阅读 2019-03-21 17:03:56
    这些功能特性包括C++11、C++14、C++17和之后接受的标准版本 (C++20/C++2a),还有几个技术规范的内容。 C++2a 功能特性 注意,此列表可能根据 C++2a 标准草案的演化而更改。 C++2a 功能特性 提案 ...
  • 使用g++编译C++ 11标准的C++代码

    千次阅读 2018-09-07 08:26:06
    使用g++编译C++ 11标准的C++代码1. 升级g++版本 仅g++ 4.8及以上版本才支持C++ 11标准。 查看g++版本: $ g++ --version2.... g++ 4.8默认不支持C++ 11标准,使用...编译使用了C++ 11标准的代码文件: $ g++ -std=c++11 t
  • 用DevC++ 运行C++ 程序时,出现如下错误error This file requires compiler and library support for the ISO ... This support is currently experimental, and must be enabled with the -std=c++11 or -std=gn...
  • 使devc++拥有c++11的功能(如何让devc++支持c++11)

    千次阅读 多人点赞 2019-01-03 15:23:14
    为什么我的devc++不能使用c++11的一些功能,为什么不能用auto,为什么。。。其实只要加一句命令就好了,具体在哪加往下看,希望对你有帮助噢。 第一步:打开devc++  第二步:点击工具选择编译选项  第三...
  • C++多线程类Thread(C++11

    万次阅读 多人点赞 2018-05-15 19:29:45
    C++多线程类Thread(C++11C++11中std命名空间将Boost库中的Thread加入,Boost的多线程从准标准变为标准,在深度学习以及应用程序开发中经常用到多线程,这里将其用法整理复习,以demo的形式复习,每次遇到问题或者...
  • VS2010、VS2012、VS2013对C++11的支持进度

    万次阅读 2014-08-07 14:03:45
    原帖地址: http://msdn.microsoft.com/zh-cn/library/hh567368.aspx
  • c++11 thread类简单使用

    万次阅读 多人点赞 2018-02-06 18:40:17
    近日项目需要用到多线程,由于之前没有写过c++代码,经过研究,发现c++11引入了一个多线程类,就写了一个简单的例子,做个简单记录。 –2018年的今天很冷,全球股市暴跌。 简单多线程例子: detch()启动线程: ...
  • 若要了解有关 Visual Studio 2017 RC 的最新文档,请参阅 ...本文描述了 Visual C++ 中的 C++11/14/17 功能。 本文内容 C++11 功能列表 C++11 核心语言功能表 C++11 核心语言功能表:并发 C++1
1 2 3 4 5 ... 20
收藏数 2,779,617
精华内容 1,111,846
关键字:

c++11