decltype和auto的区别

注意:引用类型的变量必须初始化,本文在个别示例代码中没有做详细说明,请注意。

auto

编程时候常常需要把表达式的值付给变量,需要在声明变量的时候清楚的知道变量是什么类型。然而做到这一点并非那么容易(特别是模板中),有时候根本做不到。为了解决这个问题,C++11新标准就引入了auto类型说明符,利用auto关键字,编译器会根据所赋值的类型推断变量的类型,因此,使用auto声明的变量必须初始化

所有变量的初始基本数据类型都必须是一样

auto i = 3, j = 4; // 正确

auto i = 3, j = 4.0, t = "string"; // 错误

auto会忽略顶层const/&

int i = 3, &j = i;

const int t = 4;

const int * const p = &i;

auto a = i; // a的数据类型是int 

auto b = j; // b的数据类型是int,忽略了引用修饰&

auto c = t; // c的数据类型是int,忽略了顶层const

auto d = p; // d的数据类型是const int *,忽略了顶层const,但是保留了底层const

如果需要顶层const或引用,则显式写出来

auto& b = j; // b的数据类型是int&

const auto d = p; // d的数据类型是const int * const 这种定义方式与后面的相同 auto const d = p;

auto& b = t; // b的数据类型是const int&,在&修饰的作用下,顶层const也得以保存下来。因为const属性不保存下来的话,那么可以通过变量b改变t的值,而这是不允许的。

常量引用可以绑定到字面值,非常量引用不可以绑定到字面值

auto &h = 42; // 错误

const auto &j = 42; // 正确

初始化表达式为数组时,auto关键字推导类型为指针

int a3[3] = { 1, 2, 3 };
auto b3 = a3;
cout << typeid(b3).name() << endl;
程序将输出

int *

若表达式为数组且auto带上&,则推导类型为数组类型

int a7[3] = { 1, 2, 3 };
auto & b7 = a7;
cout << typeid(b7).name() << endl;
程序输出

int [3]

decltype

有的时候我们还会遇到这种情况,我们希望从表达式中推断出要定义变量的类型,但却不想用表达式的值去初始化变量。还有可能是函数的返回类型为某表达式的的值类型。在这些时候auto显得就无力了,所以C++11又引入了第二种类型说明符decltype,它的作用是选择并返回操作数的数据类型。在此过程中,编译器只是分析表达式并得到它的类型,却不进行实际的计算表达式的值。

对于内置类型的对象,使用decltype很直观,但当参数为复合类型的时候就应该注意一些使用细节问题。

基本上decltype的作用和auto很相似,就不一一列举了。对于decltype还有一个用途就是在c++11引入的后置返回类型

decltype保留顶层const和引用修饰符&

 

int i = 3, &j = i;
const int t = 4;
const int * const p = &i;
decltype(i) a; // a的数据类型是int 
decltype(j) b = i; // b的数据类型是int&,保留了引用修饰&
decltype(t) c = t; // c的数据类型是const int,保留了顶层const
decltype(p) d = p; // d的数据类型是const int *,保留了顶层const和底层const

decltype(表达式)

解引用

如果表达式是解引用类型的,那么返回引用类型。正如我们所熟悉的那样,解引用指针可以得到指针所指对象,而且还可以给这个对象赋值。因此decltype(*pInt)的结果类型就是int&.

int i = 3;
int* pInt = &i;
decltype(*pInt) a = i; // a的数据类型是int&,因此必须初始化

变量加括号

需要注意的是,一个变量加不加括号,返回类型也可能不一样。如果decltype使用的是一个不加括号的变量,那么得到的结果就是这个变量的类型。但是如果给这个变量加上一个或多层括号,那么编译器会把这个变量当作一个表达式看待,变量是一个可以作为左值的特殊表达式,所以这样的decltype就会返回引用类型:

int i = 3;
decltype(i) a; // a的数据类型是int
decltype((i)) b = i; // b的数据类型是int&,因为是引用类型,必须初始化

赋值运算符

这里再指出一个需要注意的地方就是 = 赋值运算符返回的是左值的引用。换句话意思就是说 decltype(i = b)  返回类型为 i 类型的引用。仔细看下面这段代码:

int i = 45;
decltype(i = 42) t = i; // t的数据类型是int&,因此必须初始化,而且i的值保持不变,还是45。因为decltype不会执行括号里面的代码

decltype(数组)

int a[] = {1,2,3,4,5}
decltype(a) b; // b的数据类型是int[5],是一个数组类型,而不是数组指针类型。而且a的内存空间没有被初始化

就是因为上述的这个细节,在写函数返回值的时候就要注意类型问题。

decltype(a)function() {
    return***;
}

上述的语句就是错误的,因为很明显decltype(a) 表示以数组作为返回值,这在c++中是不允许的。

int a[] = { 1,2,3,4,5 };
decltype(a) b; // b的数据类型是int[5],是一个数组类型,而不是数组指针类型
decltype(a)* c; // c的数据类型是int[5]*
std::cout << "sizeof(a) = " << sizeof(a) << std::endl;
std::cout << "sizeof(b) = " << sizeof(b) << std::endl;
std::cout << "b的地址为:" << &b << std::endl;
c = &b;
std::cout << "c+1的地址为:" << c + 1 << std::endl;

运行结果为:

sizeof(a) = 20
sizeof(b) = 20
b的地址为:0053F9B4
c+1的地址为:0053F9C8

两个地址正好相差20个字节。这是因为c指向的是int[5],c+1是跨国了int[5]的长度,为5*4=20个字节。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

20 − 11 =

+ 4 = 10