什么是类型转换
类型转换是指把一个值从一种类型转换为另一种类型:把 double 变成 int,把 char 变成它的数字编码,或者把基类指针变成派生类指针。C++ 会自动帮你做其中一些转换,而它悄悄完成的那些,恰恰是 bug 藏身的地方。
转换分两种:自动发生的隐式转换,以及你亲手写出的显式转换。你在运算符那一页已经见过它的一个症状:整数除法。类型转换就是你掌控它的方式。
隐式转换
在一个表达式中混用不同的数字类型时,C++ 会把“较小”的类型提升为“较大”的类型,让两边匹配。这通常正是你想要的。
麻烦出现在转换朝另一个方向进行时:从较宽的类型转换到较窄的类型。这叫窄化转换,它可能悄悄丢失数据。
浮点数转整数会向零截断,而不是四舍五入,所以 3.99 变成了 3。把 300 塞进一个 char 则会溢出。很多编译器会在这里给出警告,有些则不会。当你确实想要窄化时,请用显式转换写清楚,让下一个读代码的人知道这是有意为之。
解决整数除法陷阱
需要类型转换的最常见原因就是除法。当两个操作数都是整数时,/ 执行整数除法,余数会被丢掉。
解决办法是在除法之前对一个操作数使用 static_cast<double>。一个常见错误是写成 static_cast<double>(got / total):这已经太晚了,因为转换执行时 got / total 已经是 0,所以你得到的是 0.0。要转换操作数,而不是结果。
static_cast:你的默认选择
C++ 提供了四种具名转换。95% 的时候你会用的是 static_cast<T>(value),它在相关类型之间执行定义明确的转换:数值转换、枚举转 int、把 void* 转回有类型的指针,以及在你已经知道类型时沿类层次结构向上或向下转换。
优先使用 static_cast,而不是老式的 C 风格转换 (int)balance。C 风格转换为了让代码通过编译,会尝试任何转换,包括下面那些危险的转换,所以它可能悄悄去掉 const,或者重新解释原始字节。static_cast 只允许编译器确实说得通的转换,而且冗长的 static_cast<...> 在代码审查时一搜就能找到。
// Avoid - C-style cast, no safety net:
int dollars = (int) balance;
// Prefer - explicit, checked, greppable:
int dollars = static_cast<int>(balance);
另外三种转换(谨慎使用)
其余几种转换是为特定的、范围很窄的任务准备的。只有在 static_cast 确实做不到时才用它们。
const_cast 用来去掉(或加上)const。它唯一正当的用途,是调用一个忘了把参数标记为 const 的 C 风格 API。通过 const_cast 修改一个原本声明为 const 的对象,是未定义行为。
void legacyApi(char* msg); // old API, doesn't take const
const char* text = "hello";
legacyApi(const_cast<char*>(text)); // only OK if legacyApi doesn't write to it
reinterpret_cast 重新解释原始的位模式,例如把指针当作整数地址。它不做任何转换,极其不安全;几乎总是在提示你应该重新考虑设计。
dynamic_cast 在运行时根据对象的实际类型,安全地把基类指针或引用向下转换为派生类型。它要求基类是多态的(至少有一个虚函数的类),转换不成立时返回 nullptr。
如果 a 指向的是另一种 Animal,dynamic_cast<Dog*> 就会返回 nullptr,执行 else 分支。这正是它比盲目用 static_cast 沿层次结构向下转换更安全的原因。
要避免的常见错误
- 转换结果而不是操作数。
static_cast<double>(a / b)会先把小数部分舍掉。要转换a或b。 - 以为浮点数转整数会四舍五入。 它是截断:
static_cast<int>(2.99)是2。需要四舍五入时请用std::round、std::lround等函数。 - 随手使用 C 风格转换。 它会隐藏实际发生的是哪种转换。使用
static_cast,不安全的转换会直接得到编译错误,而不是一个悄无声息的意外。 - 窄化到太小的类型。 把
300转换为char,或把一个很大的long转换为int,都会回绕或溢出。请选一个足以容纳取值范围的目标类型。
下一步:if-else
现在你已经能干净地转换和比较值了,下一步就是用它们来做决策。if-else 语句会根据条件是否为 true 执行不同的代码,它是所有分支程序的基础。
常见问题
C++ 中 static_cast 和 C 风格转换有什么区别?
像 (int)x 这样的 C 风格转换会依次尝试所有转换方式,它可能悄悄变成危险的 reinterpret_cast,或者去掉 const。static_cast<int>(x) 只执行编译器能够验证的相关类型之间的转换,所以不合理的转换会被编译器拒绝。在现代 C++ 中,始终优先使用 static_cast 而不是 C 风格转换:它更安全,也更容易搜索出来。
C++ 中如何把 int 转换为 double?
使用 static_cast<double>(x)。这在除法中最重要:5 / 2 是整数除法,结果是 2,而 static_cast<double>(5) / 2 的结果是 2.5。要在除法发生之前转换其中一个操作数;转换结果 static_cast<double>(5 / 2) 已经太晚了,得到的仍然是 2.0。
C++ 中为什么把较大的值转换为较小的类型会得到错误的数字?
转换为一个装不下该值的类型,就是窄化转换。浮点数转整数会截断小数部分(static_cast<int>(3.99) 是 3),超出范围的整数要么回绕(无符号),要么结果由实现定义(有符号)。编译器通常不会阻止你,所以要有意识地转换,并确保目标类型足够宽。