1.C++11简介
c++11简介:在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增加的语法特性篇幅非常多,我们这里没办法一一讲解,所以本节课程主要讲解实际中比较实用的语法。c++11官网介绍: C++11 - cppreference.com小故事: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。
2.统一的列表初始化
2.1.{}初始化
在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。比如:struct Point { int _x; int _y; }; int main() { int array1[] = { 1, 2, 3, 4, 5 }; int array2[5] = { 0 }; Point p = { 1, 2 }; return 0; }
C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。struct Point { int _x; int _y; }; int main() { int x1 = 1; //c++98 int x2 = { 1 }; //c++11 int x3{ 1 }; //c++11 int array1[] = { 1, 2, 3, 4, 5 }; //c++98 int array2[]{ 1, 2, 3, 4, 5 }; //c++11 int array3[5] = { 0 }; //c++98 int array4[5]{ 0 }; //c++11 Point p = { 1, 2 }; //c++98 Point p{ 1, 2 }; //c++11 // C++11中列表初始化也可以适用于new表达式中 int* pa1 = new int(0); //c++98,c++98new空间只支持new一个数据空间并初始化 int* pa2 = new int[4]{ 0 }; //c++11,c++11new空间支持直接对多个new数据空间初始化 int* pa3 = new int[4]{ 1,2,3,4 }; //c++11,c++11new空间支持直接对多个new数据空间初始化 return 0; }
创建对象时也可以使用列表初始化方式调用构造函数初始化class Date { public: Date(int year, int month, int day) :_year(year) , _month(month) , _day(day) { cout << "Date(int year, int month, int day)" << endl; } private: int _year; int _month; int _day; }; int main() { Date d1(2022, 1, 1); //c++98 // C++11支持的列表初始化,这里会调用构造函数初始化 Date d2 = { 2022, 1, 3 }; //c++98 Date d3{ 2022, 1, 2 }; //c++11 Date* p1 = new Date(1, 2, 3); //c++98,c++98new空间只支持new一个数据空间并初始化 Date* p2 = new Date[3]{ {2022,1,1}, {2022,1,1} ,{2022,1,1} }; //c++11,c++11new空间支持直接对多个new数据空间初始化 return 0; }
注:其实c++11支持{}初始化本质上是为了支持new[]的多数据初始化和容器的多数据初始化。
创建容器对象时也可以使用列表初始化方式进行初始化class Date { public: Date(int year, int month, int day) :_year(year) , _month(month) , _day(day) { cout << "Date(int year, int month, int day)" << endl; } private: int _year; int _month; int _day; }; int main() { vector<int> v1 = { 1, 2, 3, 4, 5 }; vector<int> v2 { 1, 2, 3, 4, 5 }; vector<Date> v3 = { { 2022, 1, 1 }, { 2022, 1, 1 }, { 2022, 1, 1 } }; list<int> lt1{ 1, 2, 3 }; set<int> s1{ 3, 4, 5, 6, 3 }; map<string, string> dict = { { "string", "字符串" }, { "sort", "排序" } }; return 0; }
2.2.{}初始化的实现
对于所有的内置类型和用户自定义的类型,编译器通过自动转换如下图所示(内置类型自动转换方式相同),这样就天然支持列表初始化方式进行初始化。
注:这里代码Date d2={2022,1,3}是一种隐式类型的转换(隐式类型的转换是针对单参数的构造函数,这里虽然不是单参数的构造函数但是数据用{}括起来可以认为整体是单参数的),编译器首先利用{2022,1,3}构造一个Date类型的临时对象,然后用该临时对象拷贝构造给d2,编译器进行优化会直接调用Date的构造函数使用{2022,1,3}对d2进行构造。
对于STL库中的容器类型,c++11可以使用列表初始化方式对容器对象进行初始化,其实现是借助c++11新增容器initializer_list。
initializer_list类的文档介绍:initializer_list - C++ Reference (cplusplus.com)
我们使用的{}初始化列表会被编译器识别成initializer_list,{}初始化列表里面的常量内容会存储在容器initializer_list中,如下图一所示。initializer_list容器中有迭代器,initializer_list中的迭代器只能读不能写,如下图二所示,initializer_list容器中begin和end函数返回的是const修饰的指针类型,通过迭代器可以读取initializer_list容器中的数据,如下图三所示。
c++11中,STL库的各种容器中新增了一个参数类型为initializer_list的构造函数,如下图所示分别是vector、map中该新增的构造函数,各类容器在该新增的构造函数中接收initializer_list容器中的数据也就是{}初始化列表中的数据进行初始化工作。
容器中数据初始化工作一般可分为两步,首先创建空间,然后通过迭代器遍历构造函数的initializer_list类型形参对象数据,每个数据进行insert或push_back插入操作即可。vector中该构造函数初始化工作的实现代码如下图所示。
注:
1.这里vector<int> v1={1,2,3,4}可以认为是隐式类型的转换,先通过{1,2,3,4}构造了一个vector对象,然后通过该对象拷贝构造给v1对象,编译器优化成直接用{1,2,3,4}对v1进行构造。
2.如下图所示,这里的代码v1={10,20,30}也是隐式类型的转换,首先通过{1,2,3,4}构造了一个vector对象,然后通过该对象调用赋值运算符重载函数赋值给v1对象。
3.这里我们可以看出只需要给容器的构造函数增加一个参数类型为initializer_list的构造函数,构造函数内进行开空间+数据插入的初始化工作,即可实现容器的{}初始化功能。
补充内容:STL库中每个容器的插入函数也有参数类型为initializer_list的版本,如下图一所示,那么我们使用插入函数一次就可以插入多个数据,如下图二所示。
3.声明
c++11 提供了多种简化声明的方式,尤其是在使用模板时。
3.1.auto
在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以auto就没什么价值了。 C++11中废弃auto原来的用法,将其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。int main() { int i = 10; auto p = &i; auto pf = strcpy; cout << typeid(p).name() << endl; cout << typeid(pf).name() << endl; map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} }; //map<string, string>::iterator it = dict.begin(); auto it = dict.begin(); return 0; }
3.2.decltype
关键字decltype将括号内变量的类型声明为表达式指定的类型。
3.3.nullptr
由于C++中NULL被定义成0,这样就可能回带来一些问题,因为0既能表示指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。#ifndef NULL #ifdef __cplusplus #define NULL 0 #else #define NULL ((void *)0) #endif #endif
4.范围for循环
范围for我们在前面的博客中已经进行了详细的讲解(参考博客c++入门),这里就不再赘述。
5.智能指针
智能指针部分内容较多,后面会专门写一个博客来介绍智能指针。
6.STL中一些变化
新容器:用橘色圈起来是C++11中新增的几个新容器,但是实际最有用的是unordered_map和unordered_set。这两个我们前面已经进行了详细的讲解,array(前面讲过,与静态数组相似)和forward_list(单链表)大家了解一下即可。容器中的一些新方法:如果我们再细细去看会发现基本每个容器中都增加了一些C++11的方法,但是其实很多都是用得比较少的。比如提供了cbegin和cend方法返回const迭代器等等,但是实际意义不大,因为begin和end也是可以返回const迭代器的,这些都是属于锦上添花的操作。实际上C++11更新后,每个容器的插入函数接口都新增了右值引用的版本,如下图所示。这些容器的插入函数新增的右值引用版本到底意义在哪?网上都说他们能提高效率,他们是如何提高效率的?下面第7节右值引用和移动语义部分我们会进行解答。