【c++】c++11新特性(列表初始化,initializer_list,auto和decltype,STL中的一些变化)
小编个人主页详情<—请点击
小编个人gitee代码仓库<—请点击
c++系列专栏<—请点击
倘若命中无此运,孤身亦可登昆仑,送给屏幕面前的读者朋友们和小编自己!
目录
-
- 前言
- 一、c++11简介
-
- 背景
- 介绍
- 二、统一的列表初始化
- 三、initializer_list
- 四、auto和decltype
-
- auto
- decltype
- 五、nullptr
- 六、范围for
- 七、STL中的一些变化
-
- 新容器
- 新接口
- 总结
前言
【c++】布隆过滤器、海量数据处理的应用——书接上文 详情请点击<——
本文由小编为大家介绍——【c++】c++11新特性(列表初始化,initializer_list, auto和decltype,STL中一些变化)
一、c++11简介
背景
1998年是c++标准委员会成立的第一年,本来计划以后每年更新一次标准,c++国际标准委员会在研究c++03的下一个版本的时候,一开始计划到2007年发布下一个版本,但是到2006年的时候,官方觉得肯定到2007年的时候肯定完不成c++07,并且官方觉得到2008年可能也完不成,干脆就叫c++0x,0x的意思是不知道是07年,08年还是09年完成,结果到2010年都没有完成,最后在2011年才终于完成了c++标准,所以最终命名c++11
介绍
相较于c++98/03,c++11带来了数量可观的变化,带来了约140个新特性,以及对c++03标准中约600个缺陷的修正,c++11更像是从c++98/03中孕育出来的新语言,相比较而言,c++11可以更好的用于系统开发和库开发,语法更加简洁,稳定和安全,可以很好的提升程序员开发的效率,公司在实际项目的开发中对c++11使用的比较多,所以我们要对c++11进行重点的学习。但是由于c++11的新特性比较多,所以小编只会讲解c++11标准中比较使用的一些语法(新特性)
二、统一的列表初始化
- c++98标准中,允许使用{}花括号对数组或者结构体元素进行统一的列表初始值的设定,如下
#include #include using namespace std;struct Fun{Fun(int a, int b):_a(a), _b(b){}int _a;int _b;};int main(){int arr1[] = { 1, 2, 3 };Fun f2 = { 1, 2 };//c++11中支持了多参数的隐式类型转换//先使用{ 1, 2 }构造出一个临时对象,在使用临时对象去拷贝构造f2,但是在同一语句中//编译器不允许连续的构造/拷贝出现,所以进行了优化,直接使用{ 1, 2 }构造f2return 0;}
- 在c++11中,扩大了使用花括号括起的列表的使用范围,其中使用花括号{}括起的列表进行初始化的操作称为列表初始化,使列表初始化可以用于所有内置类型和用户自定义类型,列表初始化在初始化用于自定义类型的时候会调用用户自定义类型的构造函数进行初始化,其中的=可以不省略也可以省略(小编个人建议在实际使用中不要省略=),如下
#include #include using namespace std;class Fun{public:Fun(int a, int b):_a(a), _b(b){}private:int _a;int _b;};int main(){int a = 1;int b = { 2 };//自定义类型int c{ 3 };//省略=int arr1[] = { 1, 2, 3 };//数组int arr2[]{ 1, 2, 3 };//都是调用构造函数Fun f1(1, 2);//以前的方式Fun f2 = { 1, 2 };//不省略=,使用{}对用户的自定义类型进行初始化Fun f3{ 1, 2 };//省略=int* p1 = new int(1);int* p2 = new int{ 1 };//c++11中的列表初始化也可以用于new表达式中Fun* pf1 = new Fun(1, 2);Fun* pf2 = new Fun{ 1, 2 };return 0;}
三、initializer_list
- initializer_list是模板类,用于访问{}花括号列表中的值,该{}花括号列表是由类型为const T的元素组成的列表
- 我们使用如下代码验证一下initializer_list的类型
int main(){auto il = { 1, 2, 3, 4, 5 };cout << typeid(il).name() << endl;cout << sizeof(il) << endl;return 0;}
运行结果如下
- 在c++11中使用花括号括起来的列表的类型会被编译器识别为initializer_list类型,我们可以观察initializer_list的大小在32位环境下,只有8字节,那么initializer_list的底层其实很简单,如下
template<class T>class initializer_list{private:const T* _start;const T* _finish;};
- initializer_list的私有成员变量只有两个const T*的指针,分别指向{}花括号括起来的列表的开头和结尾的下一个位置,编译器会将花括号括起来的列表的类型进行特殊处理为initializer_list,并且会分别将initializer_list中两个指针分别指向{}列表的开头的地址和结尾的下一个位置的地址,这样,容器拿到initializer_list之后,initializer_list中有{}列表的元素个数,并且支持了迭代器的begin和end,begin其实就是_start,end其实就是_finish,size和{}列表的开头和结尾的下一个位置的指针就可以使用initializer_list中的迭代器去遍历{}列表,那么容器就可以使用initializer_list去进行构造了
- initializer_list通常是作为构造函数的参数,c++11中不少容器增加了initializer_list作为构造函数的参数,这样我们进行初始化容器就简单很多,例如vector/list/map等
#include #include #include using namespace std;int main(){vector<int> v = { 1, 2, 3, 4, 5 };map<string, string> m = { {\"sort\", \"排序\"}, {\"insert\", \"插入\"} };return 0;}
- 基于小编模拟实现的vector基础上支持使用initializer_list进行构造的构造函数,关于vector的源代码小编已经放在了详情请点击<——
template<class T>vector{public:vector(initializer_list<T> lt){reserve(lt.size());//复用reserve,避免频繁扩容,减少消耗//initializer_list也支持了size(),可以获取initializer_list的元素个数for(auto e : lt)//initializer_list支持迭代器,所以可以使用范围for{push_back(e);//调用尾插将列表中的元素逐个插入即可}}};
- 分析一下使用initializer_list构造map对象,如下
- initializer_list也可以作为operator=的参数,这样就可以使用{}花括号列表对对象进行赋值
四、auto和decltype
auto
- c++11中对auto进行升级,使auto可以支持自动推导类型,这样就必须要求进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型
- auto的应用场景大多是搭配范围for进行使用
int main(){int a = 10;auto p = a;cout << p << endl;//显示初始化为10cout << typeid(p).name() << endl;//将p的类型推导设置成和a的类型return 0;}
运行结果如下
decltype
- 如果我们想要获取变量的类型可以使用typeid(变量名).name()进行获取,可是使用这种方式获取变量的类型之后仅仅只能用于打印,不能用于定义变量,使用这种方法可以说是只能看不能用
int main(){int a = 0;cout << typeid(a).name() << endl;return 0;}
- c++11中引入关键字decltype,用于将变量的类型声明为表达式指定类型
- decltype的使用场景有两点:推出变量类型,第一点:单纯的定义变量不初始化,第二点:作为模板实参(这两点都是auto无法做到的,auto定义的变量必须初始化)
template<class T>class Fun{public:private:T _a;decltype(malloc) _b;//函数指针书写不便,使用decltype定义一个函数指针};int main(){int a = 10;double b = 0.5;decltype(a) c;//单纯定义变量不初始化decltype(a * b) d;//定义表达式的类型Fun<decltype(a)> f;//作为模板的实参return 0;}
五、nullptr
- 由于在c++中NULL被宏定义为0,那么使用0初始化指针存在类型不匹配,同时在一些调用场景上会出现调用问题,同时在一些场景中传参会出现类型不匹配的问题
#define NULL 0
- 调用不明确,如下,Fun(int* ptr)和Fun(int a)构成函数重载,那么我们使用Fun(NULL)本意是想调用去匹配形参类型为指针的函数Fun(int* ptr),可是由于NULL被宏定义为0所以会去匹配Fun(int a)进行调用,这就存在的调用问题
void Fun(int* ptr){cout << \"void Fun(int* ptr)\" << endl;}void Fun(int a){cout << \"void Fun(int a)\" << endl;}int main(){Fun(NULL);return 0;}
运行结果如下
- 所以处于安全性的角度考虑,c++11中新增了nullptr表示空指针,nullptr是(void*)0类型,即将0强转为void*的地址,这样就是nullptr就表示指针类型了,指针支持随便强转为其它指针类型,并且调用上也不存在问题了
void Fun(int* ptr){cout << \"void Fun(int* ptr)\" << endl;}void Fun(int a){cout << \"void Fun(int a)\" << endl;}int main(){Fun(nullptr);return 0;}
运行结果如下
六、范围for
- 在c++11中支持了范围for这个语法糖,支持迭代器的容器都支持迭代器,并且连常规数组都可使用范围for进行遍历,范围for只支持正向遍历,并且如果遍历的容器中存储的是自定义类型,为了减少消耗应该加上&,同时如果不修改应该加上const
#include #include #include using namespace std;int main(){int a[] = { 1, 2, 3, 4, 5, 6 };for (auto e : a){cout << e << \' \';}cout << endl;vector<int> v = { 1, 2, 3, 4, 5, 6 };//这里v的初始化调用了vector中形参类型为initializer_list的构造函数初始化for (auto e : v){cout << e << \' \';}cout << endl;map<string, string> m = { {\"sort\", \"排序\"}, {\"insert\", \"插入\"} };for (const auto& kv : m){cout << kv.first << \':\' << kv.second << endl;}return 0;}
运行结果如下
七、STL中的一些变化
新容器
- c++11新增了STL的四个容器,分别是array(定长数组),forward_list(单链表),unordered_map(哈希),unordered_set(哈希)
- 其中array和forward_list是比较鸡肋的
- array相对于普通数组的越界检查严格,array可以检查出越界读和写,读写其底层是调用的operator[ ],operator[ ]对于读写检查十分严格,array的底层其实就是开了普通数组,array大小和普通数组的大小相同,但是array的优势也就仅限于此了,array通过非类型模板参数让array作为定长数组,和vector根本没有可比性的其实,vector的介绍详情请点击<——
- forward_list是单链表,但是单链表的头插操作太过简单,例如小编在实现哈希表的开散列的版本中哈希桶中的每一个桶就是采用节点中保存一个指向的单链表指针作为其底层结果,由于单链表头插的代码极易编写,所以对于单链表小编并没有使用这里的forward_list,forward_list同样相对于list也没有其它优势,无非是每一个节点中少了一个指针,forward_list节点中是一个指针,list节点中是两个指针,现代计算机硬件完善,这少了一个指针所节省的内存无关紧要,并且list还支持任意位置的插入和删除,所以forward_list属实挺鸡肋的,list的介绍详情请点击<——
- 但是对于unordered_map(哈希),unordered_set(哈希)这两个容器还是十分有用的,关于unordered_map(哈希),unordered_set(哈希)的底层封装,详情请点击<——
新接口
-
新增了一系列c开头的迭代器的,这些是由于c++标准委员会认为原始的普通对象和const对象都去调用begin和end容易混淆(其实不容易混淆)
-
c++标准委员会认为容易混淆普通对象和const对象调用的是同一个begin和end,其实不是,begin和end的普通版本和const版本构成函数重载,因为const版本的begin和end中的const修饰的是隐含的this指针,this指针是隐藏的第一个函数参数,所以参数类型不同,构成函数重载,begin和end都有普通版本的begin和const版本的begin,那么当普通对象进行调用的时候回去调用普通版本的begin和end,const对象进行调用的时候回去调用const版本的begin和end,很容易区分,所以在实际中对于c系列开头的迭代器反而使用并不多,所以c系列开头的迭代器反而也是挺鸡肋的
-
如上,所有容器均支持了{}列表初始化的构造函数
-
如上,所有容器新增了emplace系列会涉及&&右值引用和…模板的可变参数,会带来性能上的提升,小编会在后面的文章中进行详细讲解
-
如上,容器新增了移动构造(也叫移动拷贝构造)和移动赋值,同样也会带来性能上的提升,小编会在后面的文章中进行讲解
总结
以上就是今天的博客内容啦,希望对读者朋友们有帮助
水滴石穿,坚持就是胜利,读者朋友们可以点个关注
点赞收藏加关注,找到小编不迷路!