前言
很久以前就知道C++11
对我们课上讲的C++有很多改进的地方,当时也没有细学,最近一个偶然的机会陡然发现原来身边的同学好多都对C++11
都颇有心得,推崇备至,回头想想在C++14
,甚至C++17都不新鲜的现在,连C++11
都不会的话显然有点说不过去了。。。于是呢我就打算利用最近闲着的时间,在补《人民的名义》的间隙顺便学学C++11应该也是极好的。
类型推导
auto关键字
目的
auto关键字不是C++11
里诞生的关键字,在这之前,auto代表的意思是“具有全局存储期的局部变量”,限定的是变量的作用域。显然,这玩意并没什么用,于是在C++11
里,他就变成了可以自动推导的变量类型。所谓自动推导,并不意味着他就跟javascript里的var一样,颠覆了C++强类型语言的性质,他其实只是在编译的过程中由电脑来推导变量类型,在运行时是不存在auto这种变量的。
用法
我们可以用auto声明其他的变量,但是需要注意下面几点:
- auto 声明的变量必须在编译阶段就能识别类型。
- auto 不能声明非静态成员变量。
- auto 不能用来声明函数参数。
- auto 的自动推导于模板的自动推导(Template argument deduction)本质相同。
- 当auto不被声明为指针或引用时,auto的推导结果将和初始化表达式的抛弃ref(引用)和cv(const volatile)限定符的类型一致。
- 当auto被声明为引用或指针时,auto的推导结果将继承初始化表达式的cv限定符。
不过细想,有时候还是有点绕人的,比如:
1 | int x=0; |
明明写法不一样,但是推导出来的x1和x2的类型竟然是一样的。这看上去有点不可思议,不过换一个看法估计就更清楚一点:
1 | int x=0; |
对于x1,T被推导成了int,因此x1被推导成了int ;
对于x2,T被推导成了int,因此x2也被推导成了int *;
这很好理解,那么auto也就很好理解了。(虽然还是有点怪怪的感觉。。。)
用途
- 简化代码。
- 判断泛型中的变量类型。
他出现的目的,主要是由于我们懒得写那些巨长无比的类型名,最典型的用处就是在声明迭代器的时候:
1 | std::map<double,double> mp; |
这迭代器的声明太傻了,我们不喜欢,我们喜欢下面的样子:
1 | std::map<double,double> mp; |
当然,有时候我们用这个也的确是因为我们无法判断真正的类型是什么,比如:
1 | class A{ |
我们希望work函数能统一处理所有叫func的静态函数,但是由于返回值不同,所以我们不能直接指定val的类型,但是用auto就能暂时确定val的类型,用于后续处理。
decltype关键字
目的
decltype关键字使用来在编译时推导出一个表达式的类型,通常用这个结果来声明另一个变量。
用法
参照下面的例子:
1 | struct A{ |
需要注意的是当decltype的参数是函数的时候,他并不需要执行这个函数,毕竟这是在编译阶段完成的事情嘛。
用途
虽然decltype在泛型中有一些重要的用法,但是最常用的用一个类型来定义另一个类型:
1 | typedef decltype(nullptr) nullptr_t; |
这种重新定义类型的方法更加直观。
返回类型后置语法
目的
有时候我们在用模板函数的时候无法指定函数的返回值,需要通过一些参数的运算才能获得返回值类型,这时候就需要返回类型后置语法来处理了。
用法&用途
比如下面的场景:
1 | int& foo(int &i); |
显然,我们不能将???处简单的用T来替换,这看上去就很无解了,这时候就需要auto+decltype来共同处理了:
1 | int& foo(int &i); |
模板的优化
右尖号的细节处理
我们知道,在C++11之前,下面的声明是有问题的:
1 |
|
编译器会报错,他把俩右尖号看成是位移符了,我们得改成这样:
1 |
|
在C++11之后,设计者考虑了这个因素,第一种的写法也能够认了。不过带来一个小bug就是下面的语法行不通了:
1 | vector< 16>>2 >v; |
虽然没人这样写,但是他编译的时候的确会报错。实际写的时候还是加括号比较稳妥一点。
模板别名
c++11引了using的别名语法,事实上就是typedef的加强版。基本操作如下:
1 | using uint=unsigned int ; |
这不新鲜,新鲜的是using语法甚至定义泛型,这是typedef所做不到的:
1 |
|
这个用法就像生成一个新模板一样将map的key的类型固定,只露出了值的类型。
基于范围的for循环(Range-for statement)
我们知道在python之类的语言里都支持类似for i in arr
这样的for循环语法,这种语法相比显式用下标和迭代器来循环更加简洁。虽然在algorithm头文件里有一个for_each
函数做到了类似的效果,但是他仍然需要显式地指定begin()和end(),并不是真正意义上的基于范围。
在C++11里引入了基于范围的for循环,它可以支持迭代任何容器、数组、初始化列表等类型。
具体方法如下:
1 | int a[]={2,3,4}; |
除了能循环数组,vector这类容器之外,还能迭代map等特殊容器,不过需要注意的是,对于map来说,我们获得的迭代变量是pair类型的,因此我们需要用ite.first、ite.second来获得键和值:
1 | std::map<int,int>m={{1,2},{3,4}}; |
同时,这个迭代变量还可以声明成引用类型,也就是说我们可以通过他对原数组进行修改:
1 |
|
输出结果是:
1 | 2 |