C++11~20核心特性(3)auto占位符

1. 重新定义的auto关键字

  • C++98标准开始, auto用来声明自动变量,并且默认就是auto, 可以不写。

    // 前面的auto可以不写
    auto int b,c=5;
  • C++11标准赋予了auto新的含义:

    • 声明变量时,根据初始化表达式自动推断该变量的类型
    • 声明函数时,函数返回值的占位符
    auto i = 5;         // 推断为int
    auto str = "hello"// 推断为const char*
    auto sum(int a1, int a2) // 推断为int
    {
        return a1+a2;
    }

2. 使用注意事项

使用auto占位符声明变量的时候必须初始化变量,否则,编译器无法确认其具体类型,导致编译错误。

auto i; // 编译失败
i = 5;

进一步来说,有以下几点注意:

  • 一个auto声明多个变量时,编译器遵从由左往右的推导规则,以最左边的表达式推断auto的具体类型

    int n = 5;
    // auto推断为int
    auto *pn = &n, m = 10;

    但是如果写成下面的代码,将无法通过编译:

    int n = 5;
    auto *pn = &n, m = 10.0// 编译失败,声明类型不统一

    报错如下:

    'auto' deduced as 'int' in declaration of 'pn' and deduced as 'double' in declaration of 'm'

    左边根据n的类型,auto推断为int,但是m的值是10.0(double类型)。auto推断类型必须与值完全一致,int与double不一致,所以报错。

  • 当使用条件表达式初始化auto声明的变量时,编译器总是使用表达能力更强的类型

    auto i = true ? 5 : 8.0// i的数据类型为double

    虽然表达式的返回值一定是int,但是auto依旧会被推导为表达能力更强的类型double。

  • auto无法声明非静态成员变量

    class sometype {
      auto i = 5// 错误,无法编译通过
      static const auto i = 5;
      static inline auto i = 5// C++17开始,可以不用const,使用inline
    };

    静态成员变量,必须在前面加上const或者inline。不加的话,无法在声明时给定初始化值,也就无法自动推断类型。

  • C++20之前,无法在函数形参中使用auto

    void echo(auto str) // c++20开始, 支持函数形参为auto
      std::cout << str << std::endl;
      [](auto n) {}(1); // c++14开始, 支持lambda表达式形参为auto
    }
  • 可以和new结合使用

    auto i = new auto(5);
    auto* j = new auto(5);

    不建议像上面这样使用,破坏代码的可读性。后面的内容将讨论应该在什么时候避免使用auto关键字。

3. 推导规则

  • 如果auto声明的变量是按值初始化,则推导出的类型会忽略cv限定符。

    进一步解释为,在使用auto声明变量时,既没有使用引用,也没有使用指针,那么编译器在推导的时候会忽略const和volatile限定符。当然auto本身也支持添加cv限定符。

    const int i = 5;
    auto j = i;     // auto推导类型为int,而非const int
    auto &m = i;    // auto推导类型为const int,m推导类型为const int&
    auto *k = &i;   // auto推导类型为const int,k推导类型为const int*
    const auto n = j; // auto推导类型为int,n的类型为const int

    在上面的代码中,虽然i是const int类型,但是因为按值初始化会忽略cv限定符,所以j的推导类型是int而不是const int。

    而m和k分别按引用和指针初始化,因此其cv属性保留了下来。

    另外,可以用const结合auto,让n的类型推导为const int。

  • 使用auto声明变量初始化时,如果目标对象是引用,则引用属性会被忽略

    int i = 5;
    int &j = i;
    auto m = j; // auto推导类型为int,而非int&

    虽然j是i的引用,类型为int&,但是在推导m的时候会忽略其引用。

  • 使用auto和万能引用声明变量时(后续左值、右值再讨论),对于左值会将auto推导为引用类型

    int i = 5;
    auto&& m = i; // auto推导类型为int& (这里涉及引用折叠的概念)
    auto&& j = 5// auto推导类型为int

    i是一个左值,所以m的类型被推导为int&,auto被推导为int&,这其中用到了引用折叠的规则。

    5是一个右值,因此j的类型被推导为int&&,auto被推导为int。

  • 使用auto声明变量,如果目标对象是一个数组或者函数,则auto会被推导为对应的指针类型

    int i[5];
    auto m = i; // auto推导类型为int*

    int sum(int a1, int a2)
    {
        return a1+a2;
    }
    auto j = sum; // auto推导类型为int (__cdecl *)(int,int)

    虽然i是数组类型,但是m会被推导退化为指针类型。

    同样,j也退化为函数指针类型。

  • auto与初始化列表组合

    1. 直接使用列表初始化,列表中必须为单元素,否则无法编译,auto类型被推导为单元素的类型。
    2. 用等号加列表初始化,列表中可以包含单个或者多个元素,元素类型必须相同。
    auto x1 = { 12 }; // x1类型为 std::initializer_list<int>
    auto x2 = { 12.0 }; // 编译失败,花括号中元素类型不同
    auto x3{ 12 }; // 编译失败,不是单个元素
    auto x4 = { 3 }; // x4类型为std::initializer_list<int>
    auto x5{ 3 }; // x5类型为int

4. 什么时候使用auto

  • 当一眼就能看出声明变量的初始化类型时,可以使用auto。

    例如:

    std::map<std::stringint> tempMap;
    for (auto it = tempMap.cbegin(); it != tempMap.cend(); ++it) {}
    // 或者
    for (auto &it : tempMap) {}
  • 对于复杂的类型,例如lambda表达式、bind等直接使用auto。

    例如:

    auto l = [](int a1, int a2) { return a1 + a2; };

    int sum(int a1, int a2) return a1 + a2; }
    auto b = std::bind(sum, 5std::placeholders::_1);

5. 返回类型推导

  • C++14标准支持对返回类型声明为auto的推导。

    例如:

    auto sum(int a1, int a2) return a1 + a2; }
  • 注意,如果有多重返回值,那么需要保证返回值类型是相同的。

    例如:

    auto sum(long a1, long a2)
    {
      if (a1 < 0) {
        return 0;       // 返回int类型
      }
      else {
        return a1 + a2; // 返回long类型
      }
    }

    以上代码中有两处返回,返回的是int类型和long类型,这种不同的返回类型会导致编译失败。

6. lambda表达式中使用auto类型推导

  • C++14标准允许在lambda表达式的形参中使用auto,这样就得到了一个泛型的lambda表达式。

    例如:

    auto l = [](auto a1, auto a2) { return a1 + a2; };
    auto retval = l(55.0);

    在上面的代码中a1被推导为int类型,a2被推导为double类型,返回值retval被推导为double类型。

  • lambda表达式返回auto引用的方法

    auto l = [](int &i)->auto& { return i; };
    auto x1 = 5;
    auto &x2 = l(x1);
    assert(&x1 == &x2); // 有相同的内存地址

7. 非类型模板参数占位符

C++17标准允许auto作为非类型模板参数的占位符。

当然,必须保证推导出来的类型允许用作非类型模板参数,否则无法通过编译。

例如:

#include <iostream>

template<auto N>
void f()
{
  std::cout << N << std::endl;
}

int main()
{
  f<5>();   // N为int类型
  f<'c'>(); // N为char类型
  f<5.0>(); // 编译失败,非类型模板参数不能为double
}
  • 在上面的代码中,函数f<5>()中5的类型为int,所以auto被推导为int类型。
  • f<‘c’>()的auto被推导为char类型。
  • 由于f<5.0>()的5.0被推导为double类型,但是非类型模板参数不能为double类型,因此编译失败。
建站  ·  开发  ·  读书  ·  教程
↑长按或扫码关注“编程之海”公众号↑
↑长按或扫码关注“编程之海”公众号↑
0 0 投票数
文章评分
订阅评论
提醒
guest
0 评论
内联反馈
查看所有评论

0

0

154

0
希望看到您的想法,请您发表评论x