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推导类型为inti是一个左值,所以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与初始化列表组合
直接使用列表初始化,列表中必须为单元素,否则无法编译,auto类型被推导为单元素的类型。 用等号加列表初始化,列表中可以包含单个或者多个元素,元素类型必须相同。
auto x1 = { 1, 2 }; // x1类型为 std::initializer_list<int>
auto x2 = { 1, 2.0 }; // 编译失败,花括号中元素类型不同
auto x3{ 1, 2 }; // 编译失败,不是单个元素
auto x4 = { 3 }; // x4类型为std::initializer_list<int>
auto x5{ 3 }; // x5类型为int
4. 什么时候使用auto
当一眼就能看出声明变量的初始化类型时,可以使用auto。
例如:
std::map<std::string, int> 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, 5, std::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(5, 5.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类型,因此编译失败。