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成功
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作为模板参数,如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()这个函数。
** 如果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,圆括号可以忽略
#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 }
追踪返回类型配合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 (*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循环还要求迭代的对象实现++和==等操作符。