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* pint *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的内容。相信通过上面的分析和实验,你可以理解了。

总结

这篇文章的知识点:

  1. const修饰与它紧邻的类型,优先修饰其左边的类型。
  2. auto在类型自动推导时,会在语法正确的前提下尽量少添加const。
  3. 使用 const auto * 的方式,我们可以初始化一个常量指针。

你可能感兴趣的:(auto类型推导与const)