1. 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++11中,对于所有的类型(包括自定义类型和内置类型)都提供了一种统一的初始化方式:使用大括号括起来的列表(初始化列表)
- 在使用初始化列表的时候可以添加等号,也可以不添加等号
void Test1() { //C++98支持的初始化方式 int x1 = 1; //C++11支持的初始化方式 int x2{ 2 }; int arr1[] = { 1,2,3,4,5 }; int arr2[5]{ 0 }; //这种初始化方式也可以应用于new表达式中 int* pa = new int[5] {1, 2, 3, 4, 5}; }
- 同时,这种初始化也可以应用于自定义类型(类)中
void Test2() { Date d1(2023, 1, 1);//之前的创建方式 //C++11支持的列表初始化,这里会调用构造函数初始化 Date d2{ 2023, 1, 2 }; Date d3 = { 2023, 1, 3 }; //同样支持new表达式 Date* pd = new Date[3]{ {2023,1,4}, {2023,1,5}, {2023,1,6} }; }
2.2 std::initializer_list
那么列表初始化是怎么实现的呢?这就要提到initializer_list
介绍文档:cplusplus.com/reference/initializer_list/initializer_list/
initializer_list是什么类型呢?
可以看到initializer_list是一个类模板。
事实上,initializer_list是非常高效的,因为它的内部并不负责保存初始u话列表中的元素拷贝,只是存储了列表中元素的引用。
举个例子:
//不能这样使用initializer_list,因为a,b在返回时并没有被拷贝 initializer_list<int> func() { int a = 1, b = 2; return { a, b }; }
所以我们应当把initializer_list
看做保存对象的引用,并在它持有对象的生存期结束之前完成传递。
initializer_list的使用场景:
- 作为构造函数的参数,使初始化容器对象更加方便
- 作为operator=的参数,使类支持大括号赋值
这里以我们之前实现的vector为例,做一个模拟实现,让vector也能够支持{}初始化和赋值
vector(std::initializer_list<T> l)//初始化列表初始化 { _start = new T[l.size()]; _finish = _start + l.size(); _endOfStorage = _start + l.size(); iterator vit = _start; for (auto& e : l) { *vit++ = e; } } vector<T>& operator= (std::initializer_list<T> l)//初始化列表的赋值重载 { vector<T> tmp(l); swap(tmp); return *this; }
3. 声明的新方式和范围for循环
C++11也提供了很多种简化的声明方式,尤其是在使用模板的时候用处很大。
3.1 decltype
关键字decltype能够将变量的类型声明为表达式的指定类型
在之前的时候,我们想知道一个变量的类型,采用的方式是typeid().name
的方式,但是这种方式返回的是一个string,没有办法通过这种方式直接声明新的变量,但是使用decltype就可以。
// decltype的一些使用使用场景 template<class T1, class T2> void F(T1 t1, T2 t2) { decltype(t1 * t2) ret; cout << typeid(ret).name() << endl; } void Test5() { const int x = 1; double y = 2.2; decltype(x * y) ret; // ret的类型是double decltype(&x) p; // p的类型是int* cout << typeid(ret).name() << endl; cout << typeid(p).name() << endl; F(1, 'a'); }
3.2 auto&nullptr&范围for循环
关于这些知识,在博主之前的博客【C++】初识C++2(内联函数&auto关键字&范围for循环&nullptr)中有详细介绍,有感兴趣的小伙伴可以去看看,这里就不赘述了。
4. STL的变化
4.1 新容器
上图中用红色方框框起来的就是C++11的新增容器,其中unordered系列容器在之前的博客中已经有过讲解【C++】哈希——unordered系列容器&哈希概念&哈希冲突,感兴趣的小伙伴可以去看一看,这里同样就不过多赘述了。
然后就是array和forword_list,实际上着两个容器不经常使用。
array主要对标的用法就是int arr1[5] = {0}
<==>array<int, 5> arr2
void Test6() { int arr1[5] = { 0 }; array<int, 5> arr2 = { 0 }; }
二者的唯一区别就是array容器对数组越界的检查更为严格,但是在实际使用过程中,很少用到。
forword_list实际上就是一个单链表,只能进行单项操作,相对于list只是在每个元素上减少了一个指针的大小。
关于这两个容器,就不进行过多的介绍了,这里附上使用文档 array forword_list
4.2 新接口
如果再去仔细看看,就会发现每个容器中都增加了一些C++11 的方法,但是其实我们都很少用到,比如提供了cbegin和cend方法用于返回const迭代器等,但是实际上意义不大,因为begin和end也实现了const版本的函数重载,只是新增的接口让使用看起来更加的规范。
除了迭代器之外,这里还增加了emplace
和emplace_back
接口,这些是插入函数的右值引用版本,这些接口的意义就是能够提高一点效率,具体提高效率的原因是右值引用,关于右值引用我们下一节再说。
本节完……