• C++ - C++11历史 - 统一列表初始化 - aotu - decltype - nullptr - C++11 之后 STL 的改变


    目录

    C++的发展史了解

     统一列表初始化

     {}初始化

     std::initializer_list

    在 vector 的模拟实现当中加上 initializer_list 作为参数的构造函数

    声明

    aotu  

    decltype 

    nullptr

     C++11 之后 STL 的一些变化

    新容器 

     容器中的一些新方法


    C++的发展史了解

    在2003年C++标准委员会曾经提交了一份技术勘误表(简称TC1),使得C++03这个名字已经取代了C++98称为C++11之前的最新C++标准名称。

    不过由于C++03(TC1)主要是对C++98标准中的漏洞进行修复,语言的核心部分则没有改动,因此人们习惯性的把两个标准合并称为C++98/03标准。从C++0x到C++11,C++标准10年磨一剑,第二个真正意义上的标准珊珊来迟。

    相比于C++98/03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。

    相比较而言,C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率,公司实际项目开发中也用得比较多,所以我们要作为一个重点去学习。
    C++11 - cppreference.com

     而 C++ 11 并不是一蹴而就的,1998年是C++标准委员会成立的第一年,本来计划以后每5年视实际需要更新一次标准,C++国际标准委员会在研究C++ 03的下一个版本的时候,一开始计划是2007年发布,所以最初这个标准叫C++ 07。

    但是到06年的时候,官方觉得2007年肯定完不成C++ 07,而且官方觉得2008年可能也完不成。

    最后干脆叫C++ 0x。x的意思是不知道到底能在07还是08还是09年完成。结果2010年的时候也没完成,最后在2011年终于完成了C++标准。所以最终定名为C++11。

     统一列表初始化

     {}初始化

     列表初始化,C 当中的列表初始化,支持的是 数组的列表初始化,在 C++11 之后,支持对 struct (结构体)。

    这里的 列表初始化 和 构造函数当中的 初始化列表是两个语法,是不一样的,这里要区分开来。

    1. struct point
    2. {
    3. point(int x, int y)
    4. :_x(x)
    5. , _y(y)
    6. {}
    7. int _x;
    8. int _y;
    9. };
    10. int main()
    11. {
    12. // 这三种方式都可以
    13. point p0(0, 0);
    14. point p1 = { 0, 0 };
    15. point p2{0, 0};
    16. }

     上述在主函数使用三种方式构造的 point 的对象,都是要调用其中的构造函数的,所以上述三种方式是等价的。

    其实,point p1 = { 0, 0 }; 这种方式其实是一种 多参数的 隐式类型转换,而像 string str = "xxxx"; 这样的方式是一种 单参数的隐式类型转换。

     验证多参数的隐式类型转换:

    1. point& r = { 1,2 }; // 编译报错
    2. const point& r = { 1,2 }; // 编译通过

    之所以出现上述的情况,是因为 发生了隐式类型的转换,发生隐式类型的转换要 先根据其他类型来临时对象,然后在使用拷贝构造函数 构造 出我们想要的对象。而上述两种方式是使用 引用的方式来接收的,那么接收的就是 临时对象的 引用,一位临时对象具有常性,所以,普通的引用去引用临时对象,发生了 权限的放大,就会报错;所以我们上述加了 const 的引用去引用 这个 临时对象才没有报错。

    在支持 列表初始化之后,不止可以对结构体来初始化,我们一般使用 int 等等的内置类型都可以用 列表来初始化了,而且可以不写 = 符号

    1. // 下面三种int 变量的初始化结果是一样的
    2. int a = 1;
    3. int b = {1};
    4. int c {1};
    5. // 下述两种数组的初始化方式是一样的
    6. int arr[] = { 1,2,3 };
    7. int arr[] { 1,2,3 };

    支持内置类型数组,使用 new 的时候,使用 列表初始化:

    	int* prt1 = new int[3]{ 1, 2, 3 };

    同样支持 自定义类型数组,使用 new 时,使用 列表初始化:
     

    1. point p1 = { 0, 0 };
    2. point p2{0, 0};
    3. point* ptr2 = new point[2]{ {1,2}, {2,3} };
    4. point* ptr2 = new point[2]{ point(1,2),point(2,3)};
    5. point* ptr2 = new point[2]{ p1, p2 };

    当然,上述使用 列表初始化的方式,本质上是调用 构造函数吗,如果不想别人使用这样的方式来初始化的对象的话,可以在构造函数之前加一个 explicit 修饰词来修饰构造函数

    1. explicit point(int x, int y)
    2. :_x(x)
    3. , _y(y)
    4. {}

    加上这个修饰词之后,下述的初始化方式就不支持了:
     

     std::initializer_list

    cplusplus.com/reference/initializer_list/initializer_list/

     先来看下述的两行代码,使用的语法是否是一样的:
     

    1. vector<int> v1 = { 1,2 };
    2. point p = { 1,2 };

    首先明确,这两行代码是使用不同的规则来实现的。

    point 对象是使用上述的 列表初始化来实现的,调用的是构造函数;可能还有人认为vector 对象也是使用 列表初始化的方式来实现的,但是上述还可以这样写:
     

    	vector<int> v1 = { 1,2 ,3,4,5,5,6};

    这种方式放在没有这么多参数的 point()当中就不行了:

    实际上,是 C++ 当中创建一个新的容器叫做 initializer_list :

     只要是用 "{}" 括起来,都可以识别成 initializer_list

     他还有迭代器的实现,其实是一个 常量数组。这个常量数组是存储在 常量区的。

     其实 initializer_list 的实现就是两个指针,指向 常量数组的起始位置和 终止位置:
     

    1. auto il1 = { 10, 20, 30 };
    2. initializer_list<int> il2 = { 10, 20, 30 };
    3. cout << typeid(il1).name() << endl;
    4. cout << sizeof(il1) << endl;

    输出;

    1. class std::initializer_list<int>
    2. 16

        initializer_list il2 = { 10, 20, 30 }; 这句代码的本质是调用 initializer_list 的构造函数,想办法取到 { 10, 20, 30 } 这段空间的 起始地址和 结束地址。


    我们现在反观 vector 是如何实现 ,    vector v1 = { 1,2 ,3,4,5,5,6}; 这样的方式来构造对象的。

    其实 是 vector 当中专门写了 一个 支持传入  initializer_list 作为参数,来构造 vector 的构造函数:

     所以,至此就知道了为什么 vector 支持 {1,2,3,4,5}这样的方式去构造 vector 的 对象,而 point 不行。

    其实,在C++ 11 之后,就不止vector 容器支持 initializer_list 作为参数的方式来进行构造对象,而是基本都支持了 initializer_list 作为参数的构造函数。

    比如 map :

       map dict = { {"sort", "排序"}, {"left", "左边"} };

     这里的 { {"sort", "排序"}, {"left", "左边"} }; 会被识别为一个  initializer_list 。其中 {"sort", "排序"} 和 {"left", "左边"},是 一个 pair 对象,当然这里使用的是 列表初始化的方式,我们还可以使用 有名对象,和匿名对象的方式来使用。

    在 vector 的模拟实现当中加上 initializer_list 作为参数的构造函数

      其实实现也很简单,因为 initializer_list 是支持迭代器的,所以,我们可以直接先把 vector 开和 initializer_list  一样大的空间,然后使用范围for 来一一赋值就行:

    1. vector(initializer_list lt)
    2. {
    3. reserve(lt.size());
    4. for (auto e : lt)
    5. {
    6. push_back(e);
    7. }
    8. }

    声明

     C++ 的类型书写比较麻烦,声明也麻烦,特别是在加上模版之后。C++11 之后,简化了很多的声明方式。

    aotu  

     在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以auto就没什么价值了。C++11中废弃auto原来的用法,将其用于实现自动类型腿断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。

     aotu在范围for,和一些复杂的模版上经常使用;但是,如果是类型比较复杂的模版参数,使用 aotu的话当然语法是通过的,但是写上aotu之后,其他人,或者是以后自己再看这部分代码的时候可读性的就变得很差,不知道此处的 aotu 是什么类型的。所以在书写复杂模版参数的时候要把这点考虑进去。

    1. int i = 10;
    2. auto p = &i;
    3. auto pf = malloc;
    4. cout << typeid(p).name() << endl;
    5. cout << typeid(pf).name() << endl;

    输出:

     如上述例子,关于 C 当中非常难写的函数指针他也可以推导出来。

     还有需要注意的是,使用 aotu 的话,必须显示的初始化,不能像下述一样书写:
     

    auto x;

    想想也知道,此时编译器不知道 x 是什么类型变量。

    decltype 

     当我们像定义之前我们定义过的某个变量的同类型的变量,那么除了使用 aotu 之外,我们还可以使用什么来定义呢?

    使用 typeid 是不行的,因为 typeid 取出来的是 类型的字符串:

    1. auto ptr = malloc;
    2. cout << typeid(ptr).name() << endl;
    3. // 不行,编译器报错
    4. typeid(ptr).name() ptr2;

    在C++ 11 当中新推出了 decltype ,它可以推导出类型,然后使用这个类型进行初始化变量:

    1. auto ptr = malloc;
    2. cout << typeid(ptr).name() << endl;
    3. decltype(ptr) ptr2;
    4. decltype(malloc) ptr3;

     而且,aotu 的话,必须显示初始化;但是使用 decltype 的话可以只是单纯定义一个变量出来。如上述代码所示。

    它还可以作为模版的实参传入:
     

    1. auto ptr = malloc;
    2. vector<decltype(ptr)> v;

    还可以是一个 表达式的结果的类型推导:

    1. int x = 1;
    2. double y = 2;
    3. decltype(x * y) ret;

    nullptr

    在 c++11 当中为了弥补 C 当中的一个坑,所增加的一个 空指针。在C 当中的是这样的定义 NULL的:
     

    #define NULL 0

    发现,NULL 直接就替换成 0 了,不是指针类型,这是不对的,所以在C++ 11 对此进行修改:

    1. #ifndef NULL
    2. #ifdef __cplusplus
    3. #define NULL 0
    4. #else
    5. #define NULL ((void *)0)
    6. #endif
    7. #endif

     ((void *)0)  指针类型,把 0 强转为 指针类型。

    1. void func(int x)
    2. {}
    3. void func(int* p)
    4. {}
    5. int main()
    6. {
    7. int* p = NULL; // int* p = 0;
    8. func(NULL); // func(0);
    9. return 0;
    10. }

    如上所示,主函数的那种实际匹配的是 第一个func 函数,而不是 第二个。

    其实上述 NULL 的坑,宏占了一大部分,我们在书写代码的过程当中尽量要用 const ,enum ,inline 取替代宏。

     C++11 之后 STL 的一些变化

    新容器 

     用橘色圈起来是C++11中的一些几个新容器。关于 unordered_map 和 unordered_set 的介绍和模拟实现请看 下述博客:
    C++ - unordered系列关联式容器介绍 - 和 set map 的比较-CSDN博客

    C++ - 封装 unordered_set 和 unordered_map - 哈希桶的迭代器实现_chihiro1122的博客-CSDN博客

    C++ - 模版进阶 - array_chihiro1122的博客-CSDN博客
     

     我们来简单介绍一下 forward_list,这个容器 和 array 容器一样鸡肋。

    array 容器 只是相比于 C 当中的数组,在调用 operator[]() 函数时候,比如C 的 数组多了一些越界检查,但是,想要检查用 vector  不好吗?此处的 array  是静态的数组,他的本意是想要替换 C 当中的数组,但是 C++ 当中有了 vector 容器 ,array 容器就不好用了。

     forward_list的本质也是一个单链表,使用的是单向迭代器。

    这个容器鸡肋就鸡肋在,只支持 头插头删;尾差尾删不支持。因为单链表的尾删效率很低,因为删除最后一个要找到前一个;而且其中的 insert()和 erase()函数也是在指定位置之后插入/删除。在指定位置之前插入/删除,也是要找到上一个,效率也不高。

     容器中的一些新方法

    如果我们再细细去看会发现基本每个容器中都增加了一些C++11的方法,但是其实很多都是用得比较少的。
    比如提供了cbegin和cend方法返回const迭代器等等,但是实际意义不大,因为begin和end也是可以返回const迭代器的,这些都是属于锦上添花的操作。

     正常我们使用 const 版本的begin()和 end()就行了,上述的用处不大。

    新增的方法接口,就是我们上述提到过的  支持 initializer_list  做参数的 构造函数。这个构造函数很多 容器都支持了。

    不止上述的更新实际上C++11更新后,容器中增加的新方法最后用的插入接口函数的右值引用版本:

    cplusplus.com/reference/vector/vector/emplace_back/

    www.cplusplus.com/reference/vector/vector/push_back/

    cplusplus.com/reference/map/map/insert/

    cplusplus.com/reference/map/map/emplace/

    这些接口的实现,提升了效率,使用的就是 右值引用和移动语义 ,具体我们在下述进行说明。其中的 emplace还涉及模板的可变参数。

    而且,不是上述新增的接口,其中的 push_back ()也进行的了升级,上述的带来的性能提升是在 调用 emplace 的情况下,在 push_back()升级之后,我们调用 push_back ()也可以有性能提升。而且,在有些地方的提升还非常大。具体看下述对  右值引用和移动语义 的介绍。

    在C++11 之后,所有的容器都提供了 移动构造 和 移动赋值的版本,可以到达在有些情况下,深拷贝的性能提升了 约 90%。

  • 相关阅读:
    EffectiveC++-条款39:明智而审慎地使用 private 继承
    Vue3+node.js网易云音乐实战项目(五)
    ESP8266-Arduino编程实例-PCF8591数据采集驱动
    Jmeter性能测试 —— jmeter之使用ServerAgent监控服务器
    量子力学教程 第2.5章
    LeetCode - 解题笔记 - 189 - Rotate Array
    RISC-V架构——中断委托和中断注入
    MCU 的 TOP 15 图形GUI库:选择最适合你的图形用户界面(一)
    【我的创作纪念日】使用pix2pixgan实现barts2020数据集的处理(完整版本)
    Fourier分析导论——第1章——Fourier分析的起源(E.M. Stein & R. Shakarchi)
  • 原文地址:https://blog.csdn.net/chihiro1122/article/details/133578157