美文网首页
auto类型推导与const

auto类型推导与const

作者: 拔丝圣代 | 来源:发表于2022-02-05 23:34 被阅读0次

    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

          本文链接:https://www.haomeiwen.com/subject/pyoykrtx.html