在下面的简单变量定义中,潜藏着一个细微的冗余:
double d{ 5.0 };
C++ 要求为所有对象显式指定类型,于是我们把变量 d
声明为 double
。然而,用来初始化 d
的字面量 5.0
本身就具有 double
类型(其类型由字面量格式隐式决定)。
相关内容 字面量类型的确定方法见 5.2 课 —— 字面量。
当变量与其初始化式需要同一类型时,我们实际上把类型信息重复了两次。
已初始化变量的类型推导
类型推导(type deduction,又称类型推断)允许编译器根据初始化式自动推导对象类型。定义变量时,可用 auto
关键字代替显式类型:
int main()
{
auto d { 5.0 }; // 5.0 是 double 字面量,d 被推导为 double
auto i { 1 + 2 }; // 1 + 2 结果为 int,i 被推导为 int
auto x { i }; // i 为 int,x 被推导为 int
return 0;
}
- 第一行:字面量
5.0
为double
,故d
类型为double
。 - 第二行:表达式
1 + 2
结果为int
,故i
类型为int
。 - 第三行:因
i
已是int
,x
同样为int
。
警告
在 C++17 之前,auto d{ 5.0 };
会把 d
推导为 std::initializer_list<double>
而非 double
。C++17 已修复,GCC 与 Clang 亦将该修复反向移植到旧标准。若使用 C++14 或更早版本且编译失败,请改用拷贝初始化:
auto d = 5.0;
由于函数调用是合法表达式,当初始化式为非 void
函数调用时,同样可用类型推导:
int add(int x, int y) { return x + y; }
int main()
{
auto sum { add(5, 6) }; // add 返回 int,sum 被推导为 int
return 0;
}
字面量后缀可与类型推导联用,以指定特定类型:
auto a { 1.23f }; // f 后缀使 a 被推导为 float
auto b { 5u }; // u 后缀使 b 被推导为 unsigned int
带限定符的推导
使用 auto
的变量也可结合 const
、constexpr
等限定符:
int a { 5 };
const auto b { 5 }; // b 为 const int
constexpr auto c { 5 }; // c 为 constexpr int
类型推导必须有推导源
类型推导不适用于无初始化式或空初始化式的对象,也不适用于初始化式类型为 void
或不完整类型的情况:
auto a; // 错误:无法推导
auto b { }; // 错误:无法推导
auto c { foo() }; // 错误:foo() 返回 void
尽管对基本类型仅节省少许键击,但在后续课程中我们将遇到复杂且冗长的类型;此时 auto
能显著减少打字与笔误。
相关内容 指针与引用的类型推导规则更复杂,见 12.14 课 —— 指针、引用及 const 的类型推导。
类型推导会丢弃顶层 const
多数情况下,类型推导会去掉初始化式中的顶层 const
:
const int a { 5 };
auto b { a }; // b 为 int(const 被丢弃)
若需要保留 const
,需显式添加:
const auto b { a }; // b 为 const int
字符串字面量的推导
因历史原因,字符串字面量类型特殊:
auto s { "Hello" }; // s 为 const char*,而非 std::string
若需推导为 std::string
或 std::string_view
,应使用字面量后缀 s
或 sv
:
#include <string>
#include <string_view>
int main()
{
using namespace std::literals;
auto s1 { "goo"s }; // s1 为 std::string
auto s2 { "moo"sv }; // s2 为 std::string_view
}
不过,此时也可考虑不用类型推导。
类型推导与 constexpr
constexpr
并非类型系统的一部分,无法被推导;但 constexpr
变量隐式带 const
,此 const
会被丢弃,可手动加回:
constexpr double a { 3.4 };
auto b { a }; // b 为 double
const auto c { a }; // c 为 const double
constexpr auto d { a }; // d 为 const double
类型推导的利弊
优点:
- 连续定义变量时名称对齐,提高可读性:
auto c { 5 }; auto d { 6.7 };
- 强制初始化,避免未初始化变量:
auto y; // 编译错误:无法推导
- 避免意外性能损失的转换:
auto s { getString() }; // 若 getString() 返回 std::string_view,则无额外转换
缺点:
- 隐藏类型信息,IDE 虽可悬停查看,但仍易出错:
auto y { 5 }; // 本意 double,却得 int
- 若初始化式类型改变,变量类型随之改变,可能产生意外行为:
auto sum { add(5, 6) + gravity }; // add 或 gravity 类型变化,sum 也变化
现代共识:对对象使用类型推导通常是安全的,可淡化类型细节,突出逻辑。
最佳实践
- 当对象类型无关紧要或显而易见时,使用
auto
。 - 需要与初始化式不同类型,或类型对理解代码很重要时,显式写出类型。
作者注
后续课程在讲解概念或示例时,若显式类型有助于理解,将继续使用显式类型而非 auto
。