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

2024-05-14 12:44

本文主要是介绍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, 可以依照顺序给定要绑定的固定参数, 通过绑定一些固定的参数从而改变其传参的个数(类似于给个缺省值)

这篇关于C++11复习内容总结(关于C++11中一些新增的常见特性)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/988770

相关文章

C++变换迭代器使用方法小结

《C++变换迭代器使用方法小结》本文主要介绍了C++变换迭代器使用方法小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1、源码2、代码解析代码解析:transform_iterator1. transform_iterat

详解C++中类的大小决定因数

《详解C++中类的大小决定因数》类的大小受多个因素影响,主要包括成员变量、对齐方式、继承关系、虚函数表等,下面就来介绍一下,具有一定的参考价值,感兴趣的可以了解一下... 目录1. 非静态数据成员示例:2. 数据对齐(Padding)示例:3. 虚函数(vtable 指针)示例:4. 继承普通继承虚继承5.

C++中std::distance使用方法示例

《C++中std::distance使用方法示例》std::distance是C++标准库中的一个函数,用于计算两个迭代器之间的距离,本文主要介绍了C++中std::distance使用方法示例,具... 目录语法使用方式解释示例输出:其他说明:总结std::distance&n编程bsp;是 C++ 标准

Go标准库常见错误分析和解决办法

《Go标准库常见错误分析和解决办法》Go语言的标准库为开发者提供了丰富且高效的工具,涵盖了从网络编程到文件操作等各个方面,然而,标准库虽好,使用不当却可能适得其反,正所谓工欲善其事,必先利其器,本文将... 目录1. 使用了错误的time.Duration2. time.After导致的内存泄漏3. jsO

MyBatis 动态 SQL 优化之标签的实战与技巧(常见用法)

《MyBatis动态SQL优化之标签的实战与技巧(常见用法)》本文通过详细的示例和实际应用场景,介绍了如何有效利用这些标签来优化MyBatis配置,提升开发效率,确保SQL的高效执行和安全性,感... 目录动态SQL详解一、动态SQL的核心概念1.1 什么是动态SQL?1.2 动态SQL的优点1.3 动态S

新特性抢先看! Ubuntu 25.04 Beta 发布:Linux 6.14 内核

《新特性抢先看!Ubuntu25.04Beta发布:Linux6.14内核》Canonical公司近日发布了Ubuntu25.04Beta版,这一版本被赋予了一个活泼的代号——“Plu... Canonical 昨日(3 月 27 日)放出了 Beta 版 Ubuntu 25.04 系统镜像,代号“Pluc

MySQL新增字段后Java实体未更新的潜在问题与解决方案

《MySQL新增字段后Java实体未更新的潜在问题与解决方案》在Java+MySQL的开发中,我们通常使用ORM框架来映射数据库表与Java对象,但有时候,数据库表结构变更(如新增字段)后,开发人员可... 目录引言1. 问题背景:数据库与 Java 实体不同步1.1 常见场景1.2 示例代码2. 不同操作

C++ 中的 if-constexpr语法和作用

《C++中的if-constexpr语法和作用》if-constexpr语法是C++17引入的新语法特性,也被称为常量if表达式或静态if(staticif),:本文主要介绍C++中的if-c... 目录1 if-constexpr 语法1.1 基本语法1.2 扩展说明1.2.1 条件表达式1.2.2 fa

java常见报错及解决方案总结

《java常见报错及解决方案总结》:本文主要介绍Java编程中常见错误类型及示例,包括语法错误、空指针异常、数组下标越界、类型转换异常、文件未找到异常、除以零异常、非法线程操作异常、方法未定义异常... 目录1. 语法错误 (Syntax Errors)示例 1:解决方案:2. 空指针异常 (NullPoi

C++中::SHCreateDirectoryEx函数使用方法

《C++中::SHCreateDirectoryEx函数使用方法》::SHCreateDirectoryEx用于创建多级目录,类似于mkdir-p命令,本文主要介绍了C++中::SHCreateDir... 目录1. 函数原型与依赖项2. 基本使用示例示例 1:创建单层目录示例 2:创建多级目录3. 关键注