C++11复习内容总结(关于C++11中一些新增的常见特性)

C++11

统一列表初始化

  • 扩增了大括号扩起的列表(初始化列表)的使用范围,使其可以用于所有的自定义类型,以及内置类型.并且使用初始化列表时,可以添加等号(赋值号)也可以不加
    如: int s = 3; int s2 = {4}; int s3{6};
    • C++11新增的类型: initializer_list(本质即是一个固定的数组)
    • 自定义类型用列表初始化会调用其构造函数
    • STL中便于赋值, 如: vector<int>v1 = {1,2,3}
      新增了初始化列表类型之后即可在构造函数中添加对应类型的有参构造,然后传入初始化列表进行有参构造,将列表的值遍历取出赋值即可
    • 列表初始化时, 如果出现类型截断,会报警告或者错误的, 而直接声明时不会

总结: C++11之后一切对象都可以使用列表进行初始化, 但对于普通对象不建议使用列表初始化, 容器(或自定义类型)如果有需要可以使用列表初始化

声明的简化

  • auto关键字: 根据所赋值的常量进行类型推导

  • decltype关键字: 用于获取类型 获取类型后可以再用该类型声明变量
    int x = 10; decltype(x) y1 = 20.22;
    typeid(变量名): 用于获取变量类型的字符串

  • nullptr: 空指针

  • 范围for循环(底层即封装了迭代器)

    vector<int> v;
    for(auto val : v){...}
    

新增的STL容器

  • array: 静态的顺序表(fixed-size) 使用的很少
  • forward_list: 单链表 使用的也很少
  • unordered_map
  • unordered_set
  • 容器内部变化
    1. 都支持了initializer_list(初始化列表)的构造函数
    2. cbegin()、cend()系列的迭代器(const类型的迭代器)
    3. 支持了移动构造与移动赋值
    4. 右值引用参数的插入

右值引用和移动语义

什么是左值?

  • 标志: 可以取地址并且可以为其赋值的即为左值
    特例: const修饰后的左值不能赋值, 只能取地址
    • 左值可以出现在赋值符号的左边, 但不一定就能在左边, 如const修饰的左值
    • 左值引用就是给左值的引用(给左值起别名)
    • const 修饰的左值引用可以引用右值, 也可以引用右值

什么是右值?

  • 标志: 右值不能取地址

    • 右值引用即是对右值的引用(给右值取别名), 右值一定不能出现在赋值符号的左边
    • 右值引用不能直接引用左值, 但可以引用move以后的左值(move之后的变量即被转换成右值→将亡值, 即被标记成右值)
      int a = 10; int&& pa = move(a);
    • 例如: 字面常量, 函数返回值(值返回, 非左值引用返回), 即临时变量, 表达式返回值等
  • 右值类型

    1. 内置类型右值 → 纯右值
    2. 自定义类型右值 → 将亡值
  • 右值的一个小特性

    • 右值本身是不能取地址的, 但给右值取别名后, 会导致编译器将右值存储到特定的位置, 且该位置可以被取地址, 也可以被修改
    • 如字面量10无法取地址, 但int&& rr = 10;取别名后, 可以对rr取地址, 且可以修改rr的值, 如果rr不想被修改, 可以使用const int&& rr去引用
      成为右值引用之后, rr则会成为左值(可以取地址且能赋值)
  • 引用的价值 : 减少拷贝

    • 左值引用解决的问题

      • 做参数
        1. 减少拷贝, 提高效率
        2. 做输出型参数
      • 做返回值
        1. 减少拷贝, 提高效率
        2. 返回引用, 可以修改返回对象(map的operator[])
    • 右值引用解决的问题

      解决传值的场景下的多余拷贝的问题

      1. 移动构造 → 资源转移
        即例如一个返回值在返回时需要拷贝一个临时对象返回, 但使用了右值引用后,则可以直接将该返回值的内容(资源)转移到接收参数上,而不需要调用构造函数
      2. 移动赋值 → 资源移动
        由移动构造实现移动赋值, 做到仅资源转移(交换), 而无需进行拷贝

      即传递的值为右值对象时, 则可以直接调用移动构造, 转移资源出来, 而不需要利用该右值去构造新的对象后返回, 又要再将该新临时对象销毁

    • C++11新增了两个默认成员函数: 移动构造移动赋值
      (在拷贝对象需要深拷贝时, 则需要自己提供移动构造和移动赋值)

      存在需要深拷贝的内容时, 基本都会自己实现移动构造和移动赋值

      • 移动构造/移动赋值的默认生成的条件
        可以尝试使用default关键字强制生成移动构造
        Person(Person&& p) = default;
        1. 自己没有实现移动构造函数
        2. 没有实现析构, 拷贝构造, 拷贝赋值中的任意一个
      • 默认移动构造/移动赋值内所做的工作
        1. 内置类型做浅拷贝(值拷贝)
        2. 自定义类型实现了移动构造则调用其移动构造/移动赋值, 否则调用其拷贝构造/拷贝赋值
      • delete关键字: 也可以使用在强制不能使用某个默认生成的函数
        Person(const Person& p) = delete; 即不允许使用拷贝构造
        • 当把析构函数delete时, 则该类也只能在堆上创建对象
          ~Person() = delete;
        • 此时可以提供一个destroy函数释放对象的资源,而不需要调用析构函数
        • 创建出的堆上的对象时, 指向对象的指针需要显式调用
          operator delete(this)来释放空间
  • 完美转发
    可以帮助我们更高效地传递参数,并保留参数的原始类型和值类别。

    • 万能引用(引用折叠)

      void Fun(int &x) { cout << "左值引用" << endl; }
      void Fun(const int &x) { cout << "const 左值引用" << endl; }
      void Fun(int &&x) { cout << "右值引用" << endl; }
      void Fun(const int &&x) { cout << "const 右值引用" << endl; }
      // 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
      // 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,
      // 但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,
      // 我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用完美转发
      // std::forward 完美转发在传参的过程中保留对象原生类型属性 
      template <typename T>
      void PerfectForward(T &&t)
      {
          Fun(t);
         	// Fun(std::forward<T>(t));
         	//如果想要编写一个通用的模板函数,能够处理所有类型的参数并以相同的值类别转发它们,
         	//那么应该显式地使用 std::forward。如果知道函数只处理左值,那么可以不使用 std::forward。
         	//然而,在大多数情况下,使用 std::forward 是更加灵活和通用的做法。
      }
      int main()
      {
          PerfectForward(10); // 右值
          int a;
          PerfectForward(a);            // 左值
          PerfectForward(std::move(a)); // 右值
          const int b = 8;
          PerfectForward(b);            // const 左值
          PerfectForward(std::move(b)); // const 右值
          return 0;
      }
      
    • 万能引用是属于语法特性, 当右值引用出现在模板参数时, 则接收实参后都会转换成左值引用, 如果想要保持原生类型, 则需要使用 std::forward(t)包括一下形参

一些其它新增关键字

final关键字

  1. 修饰类, 类不能被继承
  2. 修饰虚函数, 虚函数不能被重写

override关键字

修饰子类函数, 检查该函数是否满足重写

emplace & emplace_back

基本所有情况都使用emplace即可

  • 使用了可变参数模板实现直接构造对象
    将可变参数包不断往下传, 然后使用形参直接在容器里构造对象
  • 不少情况下emplace都比insert/push_back高效

可变参数模板(将类型依次传入生成模板)

//Args为一个模板参数包(可以有0到任意个模板参数)
template<class ...Args>
void ShowList(Args... args){//可使用模板参数包定义一个函数参数包
	//可以使用该种方式计算传入了几个参数
	cout << sizeof...(args) << endl;
}
  • 如何取出传入的形参?

    1. 递归展开模板参数包

      // 递归终止函数
      template <class T>
      void ShowList(const T &t)
      {
          cout << t << endl;
      }
      // 展开函数
      template <class T, class... Args>
      void ShowList(T value, Args... args)
      {
          cout << value << " ";
          ShowList(args...);
      }
      int main()
      {
          ShowList(1);
          ShowList(1, 'A');
          ShowList(1, 'A', std::string("sort"));
          return 0;
      }
      
    2. 采用列表初始化的方式展开模板参数包

      template <class T>
      int PrintArg(T t)
      {
          cout << t << " ";
          return 0;
      }
      //展开函数
      template <class... Args>
      void ShowList(Args... args)
      {
          int arr[] = {PrintArg(args)...};
          cout << endl;
      }
      int main()
      {
          ShowList(1);
          ShowList(1, 'A');
          ShowList(1, 'A', std::string("sort"));
          return 0;
      }
      

lambda表达式

像函数一样使用的对象/类型

  1. 函数指针
  2. 仿函数/函数对象
    类/结构体重载了operator( )
  3. lambda表达式

语法

  • lambda表达式书写格式
    [capture-list] (parameters)mutable -> return-type { statement };
    auto add = [](int a, int b)->int{return a+b;};

    // 最简单的lambda表达式
    auto v = [](){...}
    
    1. [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[ ]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中(本定义域内)的变量供lambda函数使用。(只能捕捉当前(函数)栈帧的变量,或全局变量)
      默认捕捉的变量不支持修改, 需要添加mutable关键字
      1. [var]:表示值传递方式捕捉变量var
      2. [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
      3. [&var]:表示引用传递捕捉变量var
      4. [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
    2. (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略(有mutable则不能省略)
    3. mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
    4. returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
    5. {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

    在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

    • tips

      1. 父作用域指包含lambda函数的语句块
      2. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。比如
        1. [=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量
        2. [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
      3. 捕捉列表不允许变量重复传递,否则就会导致编译错误。
        比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
      4. 在块作用域以外的lambda函数捕捉列表必须为空。
      5. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
      6. lambda表达式之间不能相互赋值,即使看起来类型相同

底层实现

  • 底层编译器对lambda表达式的处理方式即将其处理成仿函数
  • 即如果定义了一个lambda表达式, 编译器会自动生成一个类, 该类具有一个独特的UUID(Universally Unique Identifier)标识, 并在该类中重载了operator()

包装器

  • 无包装器时, useF这个函数模板由于参数F的类型都不同, 故此实例化了三份
    包装器最大的作用即是统一了函数指针, 仿函数(函数对象)和lambda表达式的类型

    #include <functional>
    template <class F, class T>
    T useF(F f, T x)
    {
      static int count = 0;
      cout << "count:" << ++count << endl;
      cout << "count:" << &count << endl;
      return f(x);
    }
    double f(double i)
    {
      return i / 2;
    }
    struct Functor
    {
      double operator()(double d)
      {
          return d / 3;
      }
    };
    int main()
    {
      // 函数名
      cout << useF(f, 11.11) << endl;
      // 函数对象
      cout << useF(Functor(), 11.11) << endl;
      // lamber表达式
      cout << useF([](double d) -> double{ return d / 4; },11.11) << endl;
      return 0;
    }
    
  • function即为包装器, 头文件 :

    // 类模板原型如下
    template <class T> function; // undefined
    template <class Ret, class... Args>
    class **function<Ret(Args...)>**;//**语法原生支持**
    //模板参数说明:
    //Ret: 被调用函数的返回类型
    //Args…:被调用函数的形参
    //具体使用方式如下
    #include <functional>
    int f(int a, int b)
    {
        return a + b;
    }
    struct Functor
    {
    public:
        int operator()(int a, int b)
        {
            return a + b;
        }
    };
    class Plus
    {
    public:
        static int plusi(int a, int b)
        {
            return a + b;
        }
        double plusd(double a, double b)
        {
            return a + b;
        }
    
    };
    //此时以下所有原先不统一的类型都统一成了std::function<int(int, int)>类型
    int main()
    {
        // 函数名(函数指针)
        std::function<int(int, int)> func1 = f;
        cout << func1(1, 2) << endl;
        // 函数对象
        std::function<int(int, int)> func2 = Functor();
        cout << func2(1, 2) << endl;
        // lamber表达式
        std::function<int(int, int)> func3 = [](const int a, const int b)
        { return a + b; };
        cout << func3(1, 2) << endl;
        // 类的静态成员函数
        std::function<int(int, int)> func4 = &Plus::plusi;
        cout << func4(1, 2) << endl;
        //类的成员函数 -> 封装类的成员函数时需传入一个类的对象, 因为必须要有this指针才能调用到一个类的成员函数
        std::function<double(Plus, double, double)> func5 = &Plus::plusd;
        cout << func5(Plus(), 1.1, 2.2) << endl;
        return 0;
    }
    
  • 包装器对于模板效率低下的解决, 对比无包装器时的代码, 此处useF只实例化了一份

    template <class F, class T>
    T useF(F f, T x)
    {
        static int count = 0;
        cout << "count:" << ++count << endl;
        cout << "count:" << &count << endl;
        return f(x);
    }
    double f(double i)
    {
        return i / 2;
    }
    struct Functor
    {
        double operator()(double d)
        {
            return d / 3;
        }
    };
    int main()
    {
        // 函数名
        std::function<double(double)> func1 = f;
        cout << useF(func1, 11.11) << endl;
        // 函数对象
        std::function<double(double)> func2 = Functor();
        cout << useF(func2, 11.11) << endl;
        // lamber表达式
        std::function<double(double)> func3 = [](double d) -> double
        { return d / 4; };
        cout << useF(func3, 11.11) << endl;
        return 0;
    }
    

bind

// 原型如下:
template <class Fn, class... Args>
bind (Fn&& fn, Args&&... args);/* unspecified */ 
// with return type (2)
template <class Ret, class Fn, class... Args>
bind (Fn&& fn, Args&&... args);/* unspecified */ 
//Fn为要绑定的函数对象
  • 用于对function包装器的调整(调整参数顺序, 调整参数类型个数)
    • 当函数包装时, 参数出现局部不同时, 可以使用bind绑定那些固定的参数, 使得剩下的传参对象能够符合统一的格式的function包装器
    • 也可以调整函数形参的顺序来实现相同的传值却有不同的接收顺序
  • placeholders: 占位符(定义在placeholders命名空间中)
    _1代表第一个形参, _2代表第二个形参, _n代表第n个形参
  • bind第一个参数为要绑定的函数对象, 后面即为_1, *2,…,*n, 可以依照顺序给定要绑定的固定参数, 通过绑定一些固定的参数从而改变其传参的个数(类似于给个缺省值)
  • 13
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

c.Coder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值