Menu

C++ 类型转换:static_cast、隐式转换与强制转换

C++ 中的类型转换是怎么工作的:隐式转换、整数除法陷阱,以及四种具名转换(static_cast、const_cast、reinterpret_cast、dynamic_cast),还有那些会悄悄丢失数据的坑。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

什么是类型转换

类型转换是指把一个值从一种类型转换为另一种类型:把 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),超出范围的整数要么回绕(无符号),要么结果由实现定义(有符号)。编译器通常不会阻止你,所以要有意识地转换,并确保目标类型足够宽。

Coddy 编程语言插图

和 Coddy 一起学编程

开始