auto类型推导与const
auto类型推导规则
C++11中新增了使用auto进行自动类型推断的功能,从此使用容器等复杂类型时,可以简化代码,非常方便。
但一开始使用auto时,有时候会看到这样的代码:
int x = 0;
const auto *y = &x;
这十分让人迷惑,auto不是可以自动类型推导吗?为什么有时使用auto还要加上const和星号,有时候又不需要?auto所代表的类型到底包不包括const?
我们用代码实验一下,实验方法是通过IDE(我用的是CLion)的debug模式,打断点查看变量的类型。
这里分普通变量、引用、指针,三种情况分别讨论。
-
对于普通变量,auto的推导结果会忽略const修饰符。简单实验
const int x = 0; auto y = x; // y -> int 没有保留const const auto z = x; // z -> const int 额外增加了const
在调试模式下查看变量类型:
可以看到y的类型是int,忽略了const修饰。
-
对于引用,auto的推导结果会保留const修饰符。
const int x = 0; auto &y = x; // y的类型是 const int & 保留了const int a = 0; auto &b = a; // b -> int & const auto &c = a; // c -> const int &
可以看到,虽然a没有const修饰,但可以在变量c定义时额外增加const修饰。
-
对于指针,我们举一个最简单的例子:
int *a = 0; const int *b = 0; auto x = a; // x -> int* auto y = b; // y -> const int*
可以看到,b的const修饰也保留到了y上。
经过上面三种情况的讨论,我们得到了结论:
用auto初始化的变量,普通变量的const修饰会忽略,而指针和引用的const修饰会保留。
指针的const修饰
但是真的这么简单吗?我们知道,指针可以有两个const修饰,例如 const int* p
和 int *const p
代表不同的含义:
如果p的类型是 const int*
,那么无法通过p来修改q的内容,也就是12这个值。例如: *p = 7
不合法,因为无法给*p赋值。
而如果p的类型是 int *const
,那么无法修改p的内容,也就是p只能指向0x01E0,不能指向其他地址。
当然,结合二者,如果p的类型是 const int *const
则图中p和*p的内容都不可修改。
可能一开始分不清这两个const,也很容易记混。实际上,const优先修饰其左边相邻的类型,如果左边没有类型,则修饰其右边相邻的类型。所以 const int*
等价于 int const*
(const修饰的都是int),却不等于 int *const
(const修饰的是int*)。
const修饰谁,谁就不能修改。 const int*
中的const修饰int,代表int的值也就是q的值不能修改;而 int * const
的const修饰的是 int*
,代表int*的值也就是p的值不能修改。
深究auto与指针的const
回到上面的auto推导指针类型的讨论,你是否注意到,保留下来的是哪一个const?回到上面看一眼,是左边的const。那么右边的const能否保留呢?我们用下面的代码实验一下:
int *a = 0;
const int *b = 0;
int *const c = 0;
const int *const d = 0;
auto m = a; // m -> int*
auto n = b; // n -> const int* 保留了左边的const
auto p = c; // p -> int* 忽略了右边的const
auto q = d; // q -> const int* 保留左边,忽略右边的const
const auto r = a; // r -> int* const 额外增加右边的const
可以看到,右边的const是不会保留的。但定义变量r的时候,我们在auto前增加const,成了右边的const。你可能要问了,为什么我明明在auto左边加的const,结果没有变成const int* ,却跑到了右边变成了int* const?
回顾一下前面说的,const优先修饰左边相邻的类型,如果没有,则修饰其右边相邻类型。在 const auto r = a;
中,const修饰的是auto,而auto推导为int,所以const修饰的是int,也就成了 int* const
。
那么,又没有办法,把const加在左边呢?
办法是有的,我们可以这样写:
const auto * r = a; // r -> const int*
// 等价于下面的定义:
auto const * r = a; // r -> const int* ,也可以写作 int const*
我们在auto后面加一个星号,这样一来,auto推导为int,const修饰auto,也就是修饰int,而不是修饰int,就成了 const int*
了。const和auto的位置换一下可以更清楚得看到,相当于我们把 * 从auto里面拆了出来,把const放在了二者中间。
只需要记住,const如果在最左边,那么它修饰的是右边紧邻的类型。对于 const int*
,const修饰的是int,而不包括星号。
auto推导与const修饰——一个原则
回到最初的问题,auto所代表的类型到底包不包括const?前面分了三种情况讨论,其实总结下来,就是一个原则:
auto类型推导会在语法正确的前提下,尽量少包含const。
对于引用,下面的语句中,b的初始化是不合法的:
const int a = 0;
int &b = a; // 不合法
const int &c = a; // 合法
不能用一个 int& 类型引用一个const int类型的变量,因此,当使用auto自动推导时,也必须带上const;
对于指针,也是类似:
const int a = 0;
const int* const b = 0;
int* m = &a; // 不合法
int* n = b; // 不合法
const int* p = b; // 合法,左边的const不能丢,右边的可以丢
指针给指针赋值时,例如变量b包含两个const,左边的const必须保留,右边的则可以忽略。因此,使用auto进行类型推导时,也会保留左边的const。
最初的问题
回到最初的问题,在第一个例子中,为什么要使用 const auto *y = &x
这样的方式来定义y的类型?
这是因为x本身不是常量,没有const修饰。只有用这种方式,才能得到一个常量指针,保证不会通过y来修改x的内容。相信通过上面的分析和实验,你可以理解了。
总结
这篇文章的知识点:
- const修饰与它紧邻的类型,优先修饰其左边的类型。
- auto在类型自动推导时,会在语法正确的前提下尽量少添加const。
- 使用
const auto *
的方式,我们可以初始化一个常量指针。