首页 > 编程笔记 > C++笔记 阅读:25

C++ auto用法详解(新手必看)

C++ 提供了很多种数据类型,有表示整数的 int 类型,也有表示小数的 float 类型,有表示单个字符的 char 类型,也有表示字符串的 string 类型。这些意义不同、用途各异的数据类型为我们定义变量来表示现实世界中的数据提供了丰富的选择。

然而,这些数据类型在使用上有一个共同的要求,那就是在定义变量表示数据时,我们必须事先知道所要表示的数据是什么类型,到底是一个小数还是一串字符,然后才能据此确定到底是该使用 float 还是 string 来定义变量。

在开发实践中,有时我们并不能轻易确定一个变量应该具有的数据类型。例如,将某个复杂表达式作为初始值赋给一个新定义的变量时,我们往往很难确定这个表达式的数据类型,从而无法确定变量应有的数据类型。

为了解决这个问题,C++ 的最新标准提供了 auto 关键字,使用它作为某个变量定义的数据类型,编译器会根据这个变量的初始值,自动推断出这个变量合理的数据类型,而无须人为指定。

例如:
auto x = 1;         // 使用整数1对变量x进行初始化,x被推断为int类型
auto y = 1.982;     // 使用小数1.982对变量y进行初始化,y被推断为double类型
Handler GetHandler();
// 使用GetHandler()函数的返回值对变量handler进行初始化
// handler被推断为Handler类型
auto handler = GetHandler();
这里,我们在定义变量 x 时,并没有指定其具体的数据类型,而是使用 auto 作为代替。因此,编译器在编译这段代码时,会根据 1 这个初始值自动推断 x 的实际数据类型为 int。

同理,使用小数 1.982 进行初始化的变量 y 会被编译器自动推断为 double 类型;而最后的一个变量 handler 会被初始化为 GetHandler() 函数的返回值,其数据类型会被推断为 Handler 类型。

虽然 auto 关键字会根据初始值自动推断变量的数据类型,但它的使用并不会增加额外的编译时间。使用 auto 关键字的好处显而易见,就像商场的免费大促销一样,这样的优惠谁不喜欢呢?

实际上,可以把 auto 关键字看作是变量定义中的数据类型占位符,它取代了原来应指定具体数据类型的位置。在编译时,编译器会根据变量的初始值推断出变量应有的具体数据类型,然后用该具体数据类型替换掉 auto 关键字,使其成为一个普通的带有具体数据类型的变量定义。

使用 auto 关键字定义变量的形式与一般的变量定义形式并无差异,唯一的差别在于,使用 auto 关键字定义变量时必须有初始值:
auto变量名 = 初始值表达式;  // 赋值形式
// 或
auto变量名{初始值表达式}; // 初始化列表形式
这样,编译器将根据初始值表达式计算结果的数据类型推断出变量的数据类型。

通常情况下,当我们难以准确地确定变量的数据类型,或者这个变量的数据类型书写复杂时,就可以使用 auto 作为变量的数据类型来定义变量,交由编译器根据变量的初始值推断出该变量的真正数据类型。相比人脑做推断这种“苦力活”,计算机要比人脑快多了。这样做不仅省去了我们自己推断数据类型的麻烦,避免了可能的人为错误,还达到了简化代码的目的。

例如:
template <typename T>
// 数据类型vector<T>之后的“&”符号,表示其后所定义的变量是一个引用
// 引用是C++中一种访问数据的特殊方式,在稍后的7.1节中将详细介绍
void printall(const vector<T>& v)
{
    // 根据v.begin()的返回值类型自动推断变量it的数据类型
    for (auto it = v.begin(); it != v.end(); ++it)
        cout << *it << endl;
}
为了在程序表达同样的意义,如果没有 auto 关键字的帮忙,我们将不得不写成下面这种烦琐的代码形式:
template <typename T>
void printall(const vector<T>& v)
{
    for (typename vector<T>::const_iterator it = v.begin(); it != v.end(); ++it)
        cout << *it << endl;
}
除简化代码外,auto 关键字有时甚至能够帮助我们完成一些在 C++11 之前不可能完成的任务,成为一种必需。

例如,在模板函数中,当一个变量的数据类型依赖于模板参数时,如果不使用 auto 关键字,根本无法确定变量的数据类型,因为我们无法提前预知用户使用何种数据类型作为模板参数来调用这个模板函数,从而无法确定这个变量的数据类型。但是使用 auto 关键字之后,一切难题都将迎刃而解。例如:
template <typename T, typename U>
void mul(const T& t, const U& u)
{
    // ...
    // 使用 auto 关键字,编译器将根据 t 和 u 的实际数据类型
    // 自动推断变量 tmp 的数据类型
    auto tmp = t * u;
    // ...
}
在这里,变量 tmp 的数据类型应该与模板参数 T 和 U 相乘结果的数据类型相同,也就是依赖于 T 和 U 的数据类型。对于程序员来说,在编写这个模板函数时,模板参数 T 和 U 的类型尚未确定,这样变量 tmp 的类型也就无法确定。所以,我们用 auto 关键字作为占位符,占据数据类型的位置,而真正的数据类型留待编译器在最终编译的时候,根据具体给定的模板参数 T 和 U 的类型进行推断来确定。这样,就把一件原来不可能的事情变成了可能。

C++ auto的缺陷

使用 auto 关键字可以根据变量的初始值自动推断其数据类型,极大地方便了复杂数据类型变量的定义。不过,这种方式好是好,却有一个缺点,那就是每次推断得到的数据类型只能在定义变量时使用一次,无法保留下来继续多次使用。好不容易推断得到的数据类型只能使用一次,显得有点不够“低碳环保”。有时,我们需要推断得到的数据能够保留下来,并可以重复使用,用于定义多个相同类型的变量。

为了弥补这个缺点,C++ 还提供了 decltype 关键字。它的使用语法形式如下:
typedef decltype(表达式) 用户数据类型;
其中,decltype(表达式)表示这个表达式的推断数据类型(declared type),也就是这个表达式计算结果的数据类型。而 typedef 是将这个推断得到的数据类型定义为用户自定义的数据类型。换句话说,typedef 为这个推断数据类型取了一个名字,使得我们可以把它作为一个新的数据类型,用在定义变量、创建对象等任何需要数据类型的地方。

例如,我们可以用 decltype 关键字来改写上面的示例程序:
template <typename T, typename U>
void mul(const T& t, const U& u)
{
    // ...
    // 用decltype得到t*u的数据类型
    // 并用typedef关键字将其定义成一个新的数据类型M
    typedef decltype(t*u) M;
    // 用这个新的数据类型M定义指针变量(表示变量或函数地址的变量),创建M类型对象
    M* tmp = nullptr;
    tmp = new M;    // ...
}
auto 和 decltype 的作用有些相似,都可以推断某个表达式的具体数据类型。但是,两者的使用还是稍有差别:

相关文章