一、简介
在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增加的语法特性非常多,本文主要讲解实际中比较实用的语法。
二、统一的列表初始化方式
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;int x2{ 2 };int array1[]{ 1, 2, 3, 4, 5 };int array2[5]{ 0 };Point p{ 1, 2 };// C++11中列表初始化也可以适用于new表达式中int* pa = new int[4] { 0 };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()
{//C++98 构造Date d1(2024, 4, 1); // old style// C++11支持的列表初始化,这里会调用构造函数初始化Date d2{ 2022, 4, 2 };Date d3 = { 2024, 4, 3 };Date* darr1 = new Date[3]{d1,d2,d3};Date* darr2 = new Date[3]{ { 2024, 3, 23 } ,{ 2024, 3, 23 } ,{ 2024, 3, 23 } };Date* darr3 = new Date(2023,3,34);Date* darr4 = new Date{ 2023, 3, 34 };return 0;
}
温馨提示:虽然这种方式使得初始化朝着大一统的方向发展,但是博主本人依然觉得使用起来及其的别扭和不舒适,加上很多早期代码或编译器不支持这种方式,博主个人建议还是按照原来的习惯将=加上,以免出现意料之外的情况发生。
2.2std::initializer_list
而相比于我们自己定的日期类,vector定义了一个v1以后为什么可以不断往内部插入数据呢?
因为vector支持了initializer_list
2.3vector对initializer_list的应用
如果给auto il 赋值一个{}这时就会认定为这个类型为initializer_list,这个容器不实际存储数据,只是一个临时的数组,此临时数组的生命周期和initializer_list对象的生存周期相同。底层用了一个临时数组将它存起来,本质上是放到了常量区 ,里面通过两个指针,一个begin()指向数组的开头一个指针end()指向数组的结尾。验证如下:
所以这里是一个initializer_list的构造,可以传任意个数的对象,所以date调用构造是优化后的结果vector也是构造加拷贝构造调用initializer_list的构造,优化后为直接调用构造。
下图则为直接构造 。
同样的,map也支持 initializer_list的构造。
而dict1就是先将kv1,kv2两个pair传给map中的 initializer_list,变成 initializer_list<pair<const string,string>>的类型
2.4map对initializer_list的应用
而第二个直接在初始化时显示的去写,而第二个之所以可以通过编译,得益于pair的拷贝构造函数,如果直接将“sort”和“排序”传给pair走构造则是const char*类型的,我们将其展开就是如下图的效果编译器依然可以通过,因为pair的拷贝构造重载了一个类模板,并不强制要求传过来的pair必须和当前的pair是同类型,可以看到其中一个拷贝构造的参数为pair类型的引用。
实现方式如下图所示: 使用了函数模板的方式来进行拷贝构造,可用性非常强。
三、auto
auto不言而喻,在之前学习c++时就已经了解过了。
在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;
}
c++11之后的版本中auto还可以用作返回值,但博主并不推荐使用auto作为返回值,因为这样在后期回看代码时会造成非常大的阻碍。至少博主不会这样进行使用。
注意:提到auto好多同学都会想到python,认为python慢的原因和auto语法有关,实际上并不是这样,它慢的原因是因为python不编译,它是边编译边运行,它不像c++/c一样先编译好后在运行。
四、decltype
int main()
{const int x = 1;double y = 2.2;cout << typeid(x).name() << endl;cout << typeid(string).name() << endl;decltype(x) z = 1;cout << typeid(z).name() << endl;const int* p1 = &x;cout << typeid(p1).name() << endl;decltype(p1) p2 = nullptr;cout << typeid(p2).name() << endl;auto ret = func1();// 假设要用vector存func1的数据vector<decltype(ret)> v;return 0;
}
它和typeid有些许相同,typeid可以帮助我们去打印类型。可以将底层原生的类可以打出来。
关键字decltype将变量的类型声明为表达式指定的类型。可以在定义对象时进行使用,让编译器去进行推导。
这个z则会定义为int 它会自动去掉1作为常量所带的那个const,而p1则不会去掉const,因为const修饰的是p1所指向的内容而不是p1本身,而还有一种说法将其成为顶层const和底层const,底层修饰的是其所指向的内容,顶层修饰的是变量自身。
举例:假设一个函数返回一个auto类型,我们就可以用decltype进行实例化推导。
五、STL新容器
大部分在之前博客中已经介绍过了 。
forward_list则是单链表又是一个让博主想吐槽的点,没什么用,不支持双向迭代器,insert只能在当前位置之后去插入,因为没有prev所以不能头插。
array静态数组,有栈溢出的风险。