C++11中的一些新特性,用来简化代码书写,提升代码效率。
1、右尖括号 > 的改进
在C++98中,编译器会优先将 >> 解析为右移符号,因此,如果在实例化模板时出现连续两个<符号时,它们之间应该用一个空格来进行分割,避免编译错误。
在C++11中,上述问题不再存在,C++11标准要求编译器智能地去判断哪些情况下 >> 不是右移符号。例如:
template <int i> class X{};
template <class T> class Y{};
Y<X<1> > x1; //C++98成功, C++11成功
Y<X<2>> x2; //C++98失败, C++11成功
注意:如果真的出现右移符号与连续的>>的冲突(比如模板中确实想使用右移符号),可以通过使用园括号来保证右移操作优先;例如:X<(1 >> 5)> x;
2、auto类型推导
在C++11中auto不再是一个存储类型指示符(storage-class-specifier,如static、extern、thread_local等),而是作为一个新的类型指示符(type-specifier,如int\float等)来指示编译器,auto声明变量的类型必须由编译时期推导而得。
volatile和const代表了变量的两种不同属性:易失性和常量性。在C++标准中,它们常常被一起叫作cv限制符(cv-qualifier )。C++11标准规定auto可以与cv限制符一起使用,不过声明为auto的变量并不能从其初始化表达式中“带走”cv限制符,例如:
double foo();
float * bar();
const auto a = foo(); //a: const double
const auto &b = foo(); //b:const double&
volatile auto *c = bar(); //c:volatile float*
auto d = a; //d:double
auto & e = a; //e: const double &
auto f = c; //f: float *
volatile auto & g = c; //g: volative float * &
用auto来声明多个变量类型时,只有第一个变量用于auto的类型推导,然后推导出来的数据类型被作用于其他的变量,例如
auto x = 1, y = 2; //x和y的类型均为int
// m是一个指向const int类型变量的指针,n是一个int类型的变量
const auto* m = &x, n = 1;
auto i = 1, j =3.14f; //编译失败
auto o = 1, &p = o, *q = &p; //o: int, p是o的引用,q是p的指针
C++11新引入的初始化列表,以及new,都可以使用auto关键字,例如:
#include <initializer_list>
auto x = 1;
auto x1( 1 );
auto y {1}; //使用初始化列表的auto
auto z = new auto(1); //可以用于new
注意:auto使用上也有限制,以下四种情况不能推导:
** auto不能是函数的形参;
** auto不能是结构体的非静态成员变量;
** 不能声明auto数组;
** 在实例化模板时不能使用auto作为模板参数,如vector< auto > v
3、decltype
C++中存在RTTI用于运行时类型识别,但是它会带来一些运行时的开销,一些编译器会让用户选择性地关闭该特性(如XL C/C++编译器的 -qnortti, GCC的-fno-rttion ,微软的 /GR- )。
与RTTI对应,decltype则允许编译时的类型推导。
注意:decltype只接受表达式做参数,函数名不能作为其参数。
标准库中基于decltype实现的模板类result_of的作用是推导函数的返回类型,例如
#include <type_traits>
using namesapce std;
typedef double (*func) ();
int main()
{
result_of<func()>::type f; //由func()推导其结果类型
}
这里f 的类型为double,而result_of并没有真正调用func()这个函数。
decltype推导的四规则:decltype( e )
** 如果e是一个没有带括号的标记符表达式(id-expression )或者类成员访问表达式,那么decltype(e)就是e所命名的实体的类型。
** 否则 ,假设e的类型为T, 如果e是一个将亡值,那么decltype(e)为T&&。
**否则,假设e的类型为T,如果e是一个左值,那么decltype(e)为T&。
**否则,假设e的类型为T,则decltype(e)为T。
注:基本上,所有除去关键字、字面量等编译器需要使用的标记之外的程序员自定义的标记(token)都可以是标记符(identifier)。而单个标记符对应的表达式就是标记符表达式,如int arr[4]; 那么arr是一个标记符表达式,而arr[3]+0, arr[3]等则不是。
示例
int i = 4;
int arr[5] = { 0 };
int *ptr = arr;
struct S{ double d; } s;
void Overloaded( int );
void Overloaded( char ); //重载的函数
int && RvalRef();
const bool Func( int );
//规则1:单个标记符表达式以及访问类成员,推导为本类型
decltype(arr) var1; //int[5], 标记符表达式
decltype(ptr) var2; //int*,标记符表达式
decltype(s.d) var4; //double,成员访问表达式
decltype(Overloaded) var5; //无法通过编译,是个重载的函数
//规则2:将亡值,推导为类型的右值引用
decltype( RvalRef() ) var6 = 1; //int&&
//规则3:左值,推导为类型的引用
decltype( true ? i :i ) var7 = i; //int&, 三元运算符,这里返回一个i的左值
decltype( (i) ) var8 = i; //int&, 带圆括号的左值
decltype( ++i ) var9 = i; //int&, ++i返回i的左值
decltype(arr[3]) var10 = i; //int& ,[]操作返回左值
decltype( *ptr ) var11 = i; //int&, *操作返回左值
decltype( "lval") var12 = "lvar"; // const char (&)[9],字符串字面量为左值
//规则4:以上都不是,推导为本类型
decltype(1) var13; //int ,除字符串外字面常量为右值
decltype( i++) var14; //int, i++返回右值
decltype( (Func(1)) ) var15; //const bool,圆括号可以忽略
与auto类型推导不能“带走”cv限制符不同,decltype是能够“带走”表达式的cv限制符的。不过,如果对象的定义中有const或volatile限制符,使用decltype进行推导时,其成员不会继承const或volatile限制符。
#include <type_traits>
#include <iostream>
using namespace std;
const int ic = 0;
volatile int iv;
struct S{ int i; };
const S a = { 0 };
volatile S b;
volatile S* p = &b;
int main()
{
cout<< is_const<decltype(ic)>::value <<endl; //1
cout<< is_volatile<decltype(iv)>::value <<endl; //1
cout<<is_const<decltype(a)>::value <<endl; //1
cout<<is_volatile<decltype(b)>::value <<endl; //1
cout<< is_const<decltype(a.i)>::value <<endl; //0,成员不是const
cout<< is_volatile<decltype(p->i)>::value <<endl; //0,成员不是volatile
}
4、追踪返回类型
追踪返回类型配合auto与decltype会真正释放C++11中泛型编程的能力。
我们可以想到的最直观的返回类型推导应该如下:
template<typename T1, typename T2>
decltype(t1 +t2 ) Sum( T1 &t1, T2 &t2)
{
return t1+t2;
}
但是,这样写编译器在推导decltype(t1+t2)时,表达式中t1和t2都没有声明(编译器只会从左往右读入符号),按照C/C++编译器规则,变量使用前必须已经声明,因此。为了解决这个问题,C++引入了新语法——追踪返回类型,来声明和定义这样的函数。如下:
template<typename T1, typename T2>
auto Sum( T1 &t1, T2 &t2) -> decltype( t1 +t2 )
{
return t1 + t2;
}
auto占位符和 ->return_type是构成追踪返回类型函数的两个基本元素。
追踪返回类型用于赶回函数指针和函数引用的情况:
auto (*fp)() -> int; //等价于 int (*fp)();
auto (&fr)() -> int; //等价于 int (&fr)();
C++98中 的for循环如下:
#include <iostream>
using namespace std;
int main()
{
int arr[5] = {1, 2, 3, 4, 5};
int *p;
for( p=arr; p<arr+sizeof(arr)/sizeof(arr[0]); ++p )
{
*p *= 2;
}
for( p=arr; p<arr * sizeof(arr)/sizeof(arr[0]); ++p )
{
cout << *p << '\t';
}
}
用C++标准库中的for_each模板函数,改造上述循环,代码如下:
#include <algorithm>
#include <iostream>
using namespace std;
int action1( int & e) { e*=2; }
int action2( int & e) { cout << e << '\t'; }
int main()
{
int arr[5] = {1, 2, 3, 4 ,5};
for_each( arr, arr+sizeof(arr)/sizeof(arr[0]), action1);
for_each( arr, arr+sizeof(arr)/sizeof(arr[0]), action2);
}
C++11中基于范围的for循环如下:
#include <iostream>
using namespace std;
int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
for( int & e : arr)
e *= 2;
for( int & e :arr)
cout<< e <<'\t';
}
注意:要使用基于范围的for循环,必须依赖如下条件:
** for循环迭代的范围是可确定的,对于类来说,如果该类有begin和end函数,那么begin和end之间就是for循环迭代的范围;
** 基于范围的for循环还要求迭代的对象实现++和==等操作符。