让编译器来选类型
上一篇中你看到,每个 C++ 变量都有一个固定的类型:int、double、std::string 等等。对于 int count = 0;,手写类型没什么问题,但类型名一长,就会显得啰嗦。auto 关键字(C++11 及以后)让编译器根据你赋的值推导出类型,你只需写一次值,剩下的交给编译器。
关键在于:auto 不是动态类型。每个变量仍然只有一个具体的类型,在编译时就确定了。auto 只是省去了你把它写出来的麻烦。
auto 的价值所在
对于简短、明显的类型,auto count = 0; 和 int count = 0; 一样好读。auto 真正发挥作用的地方,是那些又长又重复的类型名,也就是标准库的容器和迭代器带来的那种类型。
比较一下冗长的写法和 auto 的写法:
// Without auto - the type is written twice, in effect
std::vector<std::pair<std::string, int>>::iterator it = scores.begin();
// With auto - the compiler already knows the type
auto it = scores.begin();
两者声明的迭代器类型完全相同。第二种更好读,而且以后如果你把 scores 换成别的容器,它也不会和容器类型对不上。
放在一个完整的程序里是这样的:
基于范围的 for 循环中的 auto
你最常遇到 auto 的地方是基于范围的 for 循环。你几乎从来不想手写元素类型,而 auto 的写法决定了你得到的是副本还是引用。
你会看到三种写法,含义分别是:
for (auto x : v):x是每个元素的副本。对int来说开销很小,对大对象来说则是浪费。for (auto& x : v):x是一个引用;你可以原地修改元素。for (const auto& x : v):x是一个只读引用。只需要读取时就用这种写法。
下面这个程序通过 auto& 修改容器:
陷阱:如果在这个循环里写成 for (auto n : nums)(不带 &),n *= 10 就只会悄悄修改副本,nums 保持不变。编译器不会警告你,这个循环只是什么有用的事都没做。
auto 会去掉什么
普通的 auto 推导类型的方式和按值传递的函数参数相同:它会去掉顶层 const、引用和 volatile。这意味着,除非你另有要求,auto 总是给你一个全新的、可修改的副本。
如果你想保留 const 或避免拷贝,就自己加上限定符。做法是像修饰任何类型那样修饰 auto:
所以 auto 推导的是基础类型;&、const 和 * 是你在它之上添加的修饰。auto 是类型本身,const auto& 是指向它的只读引用。
常见错误和陷阱
auto 省去的是打字的工夫,而不是理解类型的必要。有几个陷阱常让新手中招:
必须初始化。 空的声明里没有任何东西可供 auto 推导,所以下面这行会直接编译报错:
auto x; // error: declaration of 'auto x' has no initializer
auto y = 0; // fine
整数字面量是 int,不是 double。 auto half = 1 / 2; 推导出 int 并存入 0,因为在 auto 看到它之前,1 / 2 已经按整数除法算完了。类型跟着值走:
auto 会去掉引用,小心意外的拷贝。 如果一个函数返回引用,而你用普通的 auto 接收它,得到的是一个副本,在热循环中这有时会成为真正的性能 bug(每次迭代都深拷贝一个大对象)。当你的意思是“只看不拿”时,就用 const auto&。
类型重要时不要把它藏起来。 如果从上下文就能明显看出 compute 的返回类型,auto result = compute(); 没有问题;但如果读者必须到处翻找才能知道 result 到底是什么,明确写出类型可能是更友好的选择。auto 是用来减少噪音的,而不是用来隐藏意图的。
下一篇:常量和 const
你已经看到,auto 会有意去掉 const,除非你要求保留它。这就引出了一个显而易见的问题:const 到底保证了什么,什么时候应该把一个值标记为不可修改?下一篇将深入讲解 const、常量表达式,以及为什么“默认加上 const”是 C++ 中最有用的习惯之一。
常见问题
C++ 的 auto 关键字有什么作用?
auto 让编译器根据初始值推导变量的类型。auto x = 5; 让 x 成为 int;auto y = 3.14; 让 y 成为 double。类型在编译时就确定了:auto 不是动态类型,而是替你写出类型的一种简写。
C++ 的 auto 会保留 const 和引用吗?
不会。普通的 auto 会去掉顶层 const、引用和 volatile。如果源是 const int& r,那么 auto x = r; 得到的是一个普通的 int 副本。要保留它们,就得明确写出来:用 const auto& 绑定一个只读引用,而不进行拷贝。
可以用 auto 声明变量而不初始化吗?
不可以。auto x; 会编译报错,因为没有初始值可供编译器推导类型。每个 auto 变量都必须在声明时就赋值。