C++ 类型转换:从「隐式转换」到「四种命名转换」
C++ 的类型转换分为两类:隐式转换(编译器自动做)和显式转换(程序员主动写)。其中显式转换又分为 C 风格 (T)expr 和 C++ 风格的四种命名转换 static_cast / dynamic_cast / const_cast / reinterpret_cast。
核心原则:能用隐式就别显式;显式里能用
static_cast解决的,就别用reinterpret_cast。
一、隐式转换
不需要任何运算符,当一个值赋给另一个兼容类型时会自动发生。
1.1 数值类型自动提升
1 | int a = 2; |
1.2 派生类指针 → 基类指针
1 | class A {}; |
向上转型(派生 → 基类)永远是安全的,C++ 默认允许。
1.3 构造函数也能引起隐式转换
如果类 B 有个「接受 A 类型」的构造函数,B b = a; 就会自动调用:
1 | class A {}; |
容易引发意外。防止办法:构造函数前加
explicit:explicit B(const A& a);,这样只能显式B b(a);才能调。
二、显式类型转换(C 风格)
C 风格转换最古老,但也最"暴力"——编译器会按顺序尝试各种转换,直到找到能成功的为止,编译器意图不明确,代码难维护。
1 | int a = 2; |
C++ 推荐用下面四种命名转换——意图明确、编译器能查错。
三、C++ 四种命名转换
1 | dynamic_cast<目标类型>(表达式) // 动态转换(运行时检查) |
3.1 static_cast:最常用的"静态"转换
编译时检查,不做运行时检查。覆盖大多数"合理"的转换:
- 数值类型互转(
int ↔ double、enum ↔ int); - 向上转型(派生 → 基类)和向下转型(基类 → 派生,但不要求多态);
void*→ 其他类型指针;- 调用显式构造函数 / 类型转换运算符。
1 | double d = 3.14; |
几乎所有"能用
static_cast的场景,都不要用 C 风格转换——意图更明确。
3.2 dynamic_cast:运行时类型检查的向下转型
只能用于指针或引用。主要用来做"多态类的向下转型"——基类指针 → 派生类指针。
要求:基类必须至少有一个虚函数(多态类)。
1 |
|
如果 Base* 实际指向的是 Derived 对象,转换成功;否则返回 nullptr(指针)或抛 std::bad_cast(引用)。
1 | int main() { |
为什么需要虚函数?
dynamic_cast运行时检查要查询对象的实际类型,靠虚函数表(vtable) 完成,没虚函数就没有类型信息。对引用:转换失败会抛
std::bad_cast异常。
3.3 const_cast:唯一能改 const / volatile 的转换
唯一能添加或去除 const / volatile 属性的转换。被转换的类型必须是指针或引用。
1 | void print(char* str) { |
⚠️ 不代表被转换的常量真的能被修改。试图修改字符串字面量(位于只读内存)→ 运行时
segment fault。const_cast的合法用途:对接老旧 C 接口、不修改对象的语义场景。
3.4 reinterpret_cast:位级别强制重解释
最危险。把指针 / 引用的二进制位重新解释成另一种类型,不做任何类型检查。
1 | class Base { public: virtual ~Base() = default; }; |
典型应用场景:
- 与底层硬件 / 操作系统打交道(如把某块内存当成特定结构体)
- 网络协议、多媒体字节流处理
- 自定义内存分配器
⚠️ 除非真的需要操作字节级别,否则永远不要用
reinterpret_cast。
四、四种转换对比
| 转换 | 检查时机 | 用途 | 危险性 |
|---|---|---|---|
static_cast |
编译期 | 数值、继承上下行、void* | ⭐⭐ 低 |
dynamic_cast |
运行时 | 多态类的安全向下转型 | ⭐⭐⭐ 中(要求虚函数) |
const_cast |
编译期 | 去掉 / 加回 const |
⭐⭐⭐ 中(误用会 UB) |
reinterpret_cast |
无 | 位级强制重解释 | ⭐⭐⭐⭐⭐ 高 |
五、RTTI(Run-Time Type Information)
运行时类型信息。dynamic_cast 和 typeid 都依赖 RTTI 才能正常工作。
RTTI 是编译器在编译期生成的类型信息,由编译器开关控制;大多数编译器默认打开,GCC / Clang 用
-fno-rtti可以关闭(但关掉后dynamic_cast/typeid不能用)。
六、易错点清单
- C 风格转换滥用:
(T)expr会按顺序尝试各种转换,意图模糊。全部换成命名转换。 dynamic_cast用在非多态类:基类没有虚函数,编译直接报错。dynamic_cast用在引用上失败抛异常:用指针版本安全,返回nullptr就知道失败。const_cast改字面量字符串:运行时崩溃;const_cast只用来"骗过编译器",不代表能改。reinterpret_cast当成普通指针转换用:高位指针 / 整数 / 类指针乱转,类型安全 = 0,代码难维护。- 继承向上转型用
static_cast多此一举:派生 → 基类本来就是隐式的,不写任何转换就行。
七、极简速记
| 场景 | 用哪个 |
|---|---|
数值类型互转、int ↔ double |
static_cast |
| 派生 → 基类(向上转型) | 隐式,不用写 |
| 基类 → 派生(向下转型,需要类型安全检查) | dynamic_cast(基类必须多态) |
| 调用显式构造函数 / 用户自定义转换 | static_cast |
临时去掉 const |
const_cast(谨慎使用) |
| 字节级别重新解释、硬件 / 网络协议 | reinterpret_cast(最后手段) |
八、小结
- 隐式转换:编译器自动做,仅限兼容类型;
- C 风格
(T)expr:能用但意图模糊,项目里最好别用; - 四种命名转换明确分工:
static_cast→ 大多数场景;dynamic_cast→ 多态向下转型(运行时检查);const_cast→ 去 / 加const(谨慎);reinterpret_cast→ 位级重解释(最后手段)。
- 现代 C++ 优先使用命名转换,让代码读起来"知道在想什么"。