在C++11中,C++标准协会引入了两个关键字,分别是:auto、decltype。这两个关键字实现了类型推导,使用这两个关键字不仅能获取复杂的数据类型,还能使代码更简洁,提高代码编写效率。
一、auto类型推导
1 酒壶装新酒
auto关键字并不是C++11新增的,在之前的版本中auto像幽灵一样的存在,它只是默默的躺在一个角落,等待着我们的临幸。终于有一天,它引起了人们的注意,这一次是不幸也是机会,它改变了原有的装饰换上了新的外衣。
在C++98/03中,auto一直是作为存储型关键字,代表着“具有自动存储期的局部变量”,乍听上去,十分的高大上,但是它的作用却非常小,很少被大家使用,主要是因为非静态型的局部变量本身就是具备“自动存储期的”。在老的版本中auto关键字使用方法如下:
auto int i=0;//可以直接使用 int i=0;进行替换
也正是因为这种原因,aotu关键字在最新的C++11中被赋予了新的含义,这一次它将作为类型指示符的形式出现,用来提示编译器对此类型的变量进行类型推导。就像是齐天大圣身披五彩霞衣,脚踏七彩祥云而来。
2 auto的推导规则
auto的推导规则总体来说有两个,分别是:
-
当不声明为指针或者引用时,auto的推导结果和初始化表达式将抛弃引用和cv限定字符类型一致。
-
当声明为指针或者引用时,auto的推导结果将保持初始化表达式的cv属性。
为了更好的理解,下面通过几个例子进行说明:
int x=0;
auto *a = &x;
auto b=&x;
auto &c = x;
auto d=c;
const auto e=x;
auto f=e;
const auto &g=x;
auto &h=g;
由事例可知:
-
a和c中的auto被替换成了int,a和c被推导为int *
-
b在推导时被推导出为指针类型
-
d被赋值为一个表达式引用时,被直接推导成了int值类型
-
e中的const auto会直接替换成const int
-
f中的赋值表达式带有const限定符时,会直接将const属性抛弃,变成int
-
g和h说明,当auto和&结合使用时,编译器的推导结果将保留原有的const属性。
3 auto的限制
auto的使用限制主要如下:
-
auto关键字是不能使用在函数参数中的
-
auto不能应用于非静态成员变量
-
auto无法定义数组
-
auto无法推导出模板参数
4、auto的使用场景
-
遍历容器的循环体中
#include<map>
int main(){
std::vector<int> v;
//...对容器进行赋值
for(auto it=v.begin();it!=v.end();it++){
//do some thing
}
return 0;
}
大家可以思考下,相对老的迭代器的定义C++11的写法和原来的有什么不同?
-
泛型函数
class Foo{
public:
static int get(){
return 0;
}
};
class Bar{
public:
static const char *get(){
return "0";
}
};
template <class A>
void func(){
auto val = A::get();
}
int main(){
func<Foo>();
func<Bar>();
return 0;
}
在上面的代码中,定义了一个泛型函数func,能够调用具有静态方法get的类型A,在get返回值后做统一的处理。如果不使用auto,则需要在新增一个模板参数,在调用get后针对不同的返回类型做对应的处理。
auto在褪去了旧衣衫后被赋予了新的含义,具有强大的功能,但是他也同时具有两面性,用的不欠当则会使代码的可读性和可维护性变得糟糕。因此大家在使用auto的时候要考虑它带来的价值和烦恼。
二、decltype推导类型
C++11中新增了decltype关键字,主要的功能是在编译时推导出一个表达式的类型,它的语法格式如下:
decltype(exp) //exp为一个表达式
1 decltype的推导规则
decltype的推导规则如下:
-
exp是标识符或者类访问表达式时,decltype(exp)的结果和exp表达式一致
-
exp是函数调用时,decltype(exp)的结果是函数的返回值
-
除上述两种外,如果exp是一个左值,则decltype(exp)的结果是exp类型的左值引用,否则和exp类型一致
按照上面的规则,借助代码可以理解如下:
1)标识符是表达式或者类访问表达式时
class Foo{
public:
static const int number=0;
int x;
};
int n=0;
volatile const int &x=n;
decltype(n) a=n;//a为int型
decltype(x) b=n;//b的类型为volatile const int &
decltype(Foo::number) c=0;//c的类型为const int
Foo foo;
decltype(foo.x) d=0;//d为整型
由上面的事例可以得知,上面的类型推导结果是符合规则1的。
2)函数调用
int func_int_r();//返回值为整型
int &func_int_rr()//返回值为整型地址
decltype(func_int_r()) x=0;//x为整型
decltype(func_int_rr()) y=0;//y为一个整型的引用,既:y--> int &
3)带括号的表达式和加法运算表达式
struct Foo{int x;};
const Foo foo = Foo();
decltype(foo.x) a=0;//a为int
decltype((foo.x)) b=a; //b为const int &
int n=0,m=0;
decltype(n+m) c=0;//c为int
decltype(n+=m) d=c;//c为int &
从上面的例子可以看出,给表达式加上一对括号,decltype推导出的类型是不一样的,a,c是符合规则1的,b,d符合规则3。
2 decltype的实际应用
在实际的编程中,decltype多应用余泛型,请看下面的代码:
template <class Container>
class Foo{
decltype(Container().begin()) iter;
public:
void func(Container &container){
iter = container.begin();
}
//....
};
使用decltype编写泛型是不是简单的多了,不用再像老的C++标准一样,如果想要迭代器支持更多的类型。还需要单独进行编写。
3 auto和decltype结合使用,返回类型后置语法
在C++11中,增加了返回类型后置语法,又称为:跟踪返回类型,具体实现是将auto和decltype结合起来进行使用,共同完成返回值类型的推导。如下代码所示:
template <typename T,typename U>
auto add(T t,U u) -> decltype(t+u){
return t+u;
}
为了帮助大家理解,再举一个例子:
int &foo(int &i);
float foo(float &f);
template <typename T>
auto func(T &val) -> decltype(foo(val)){
return foo(val);
}
通过代码可以看出,下面的例子使用老版本的C++代码是不可能完成的,返回值类型后置语法主要是为了解决函数返回值类型依赖参数而导致的返回值类型不确定的情况,有了这种语法,就可以清晰的解决此类问题,避免C++98那种晦涩难懂的语法。