C++中的显式类型转换
| cast 类型 | 用途 | 安全性 | 常用程度 | 学起来一句话总结 |
|---|---|---|---|---|
static_cast | 编译期安全转换 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 能编译过的正常类型转换基本用它 |
reinterpret_cast | 位级别重解释 | ⭐ | ⭐ | 允许你“把东西当成另一种东西看” |
const_cast | 去掉 / 添加 const | ⭐⭐⭐ | ⭐⭐⭐ | 唯一可以丢掉 const 的 cast |
dynamic_cast | 运行时检查 RTTI 的安全向下转型 | ⭐⭐⭐⭐⭐ | ⭐⭐ | 专为多态类型设计 |
1️⃣ static_cast —— 最常用、相对安全的转换
✔ 用途
- 基本类型转换(如 int → double)
- 有继承关系的向上转型(Child* → Parent*)
- void* → T*(安全的 static 转)
- 调用显式构造函数
✔ 特性
- 编译期检查(不允许危险、不合理转换)
- 不能去 const
- 不能做不相关类型的指针互转(比如 int* → double*)
double d = static_cast<double>(5); // OK Parent* p = static_cast<Parent*>(child_ptr); // 向上转换安全 void* vp = &d; double* dp = static_cast<double*>(vp); // OK,static_cast可用于 void* ❌ 错误示例(会编译失败)
int* ip = static_cast<int*>(some_double_ptr); // 不允许,不安全 一句话总结:
能用 static_cast 的地方,就不用别的 cast,它最“正统”。2️⃣ reinterpret_cast —— 最危险但最强的转换
✔ 用途
- 任意指针之间互转(int* → double*)
- 指针 → 整数(uintptr_t) → 指针
- 将一块内存按另一种类型解释(“类型重解释”)
✔ 特性
- 底层二进制不变,只改变解释方式
- 没有安全检查
- 很容易造成未定义行为(UB)
- 多用于:
- 底层系统编程(驱动、内核代码)
- 内存池
- 自己写序列化
- 跨语言 / 跨库的 API
int i = 0x11223344; char* p = reinterpret_cast<char*>(&i); // 获取字节级别内容 long addr = reinterpret_cast<long>(p); // 指针 <-> 整数 ❌ 错误风险
double* dp = reinterpret_cast<double*>(p); double x = *dp; // 未定义行为!因为内存不是 double 布局 一句话总结:
reinterpret_cast 是“霸王硬上弓”,能做任何转换,但你必须保证安全性。
3️⃣ const_cast —— 唯一能修改 const 属性的转换
✔ 用途
- 去掉 const
- 去掉 volatile
- 添加 const(极少用这个)
✔ 特性
- 只能改变对象的 const / volatile 修饰,不改变类型本身
- 必须非常小心:
移除 const 后修改对象 → 未定义行为(UB)
✔ 示例:去掉 const
void f(const int* p) { int* q = const_cast<int*>(p); *q = 10; // ⚠ 若 p originally 指向的是 const 对象,UB! } 所以 const_cast 只有一个真正安全的用途:
当你知道对象原本不是 const,只是接口要求参数是 const 时。
比如 API:
void foo(const int* x) { int* p = const_cast<int*>(x); // 只要你保证 x 指向的不是 const 对象 } 一句话总结:
const_cast 专门用来处理“去 const”的场景,不做类型转换。
4️⃣ dynamic_cast —— 多态体系中的安全向下转型
✔ 用途
- 子类 / 父类之间安全的类型转换
- 只能用于 带虚函数(有 RTTI) 的类层次结构
✔ 向下转型(Parent → Child)
Parent* p = new Child(); Child* c = dynamic_cast<Child*>(p); // OK ✔ 失败时会返回 nullptr(指针版本)
Parent* p = new Parent(); Child* c = dynamic_cast<Child*>(p); // c == nullptr,安全! ✔ 运行时检查(RTTI)
- 会查对象的实际类型
- 是唯一一个带“运行时成本”的 cast
✔ 引用版本失败会抛异常
Parent& p = Parent(); Child& c = dynamic_cast<Child&>(p); // throws std::bad_cast 一句话总结:
dynamic_cast 是唯一“真正安全”的向下转型方式,用于多态。
重点区分static_cast和reinterpret_cast
🟦 static_cast —— 语义转换,真正变成新的类型
✔ static_cast 会做“逻辑正确的”类型转换
- int → double(改变值,类型变了)
- Derived* → Base*(安全的向上转型)
- void* → T*(但你保证原本就是 T)
✔ static_cast 会让变量成为目标类型(有意义的转换)
例如:
int i = 42; double d = static_cast<double>(i); // d == 42.0 这里:
- 内存重新组织(int → double)
- 表示的数据类型改变
- 语义上是新值
再比如:
Derived* d; Base* b = static_cast<Base*>(d); 这里:
- 类型系统认可
- 行为可预测
- 对象仍然属于 Derived,只是你看它“是个 Base 指针”
static_cast 永远不会破坏对象布局,是安全的语义转换。
🟥 reinterpret_cast —— 不改变内存,只改变解释方式
✔ 它不会“改变类型”,只是“改变阅读器”
想象你拿一本书(内存):
- static_cast:把内容按照需求重新排版成新书
- reinterpret_cast:不改内容,换一副不同的眼镜(把文字当成别的语言看)
int x = 0x11223344; char* p = reinterpret_cast<char*>(&x); 内存布局:
44 33 22 11 (在小端序机器上) 你只是 用 char 的方式读它的内容*,而不是把 int 转成 char。
再看危险例子:
int* ip = new int(10); double* dp = reinterpret_cast<double*>(ip); double d = *dp; // ❌ 未定义行为 原因:
- 你告诉编译器:“这块 int 的内存,是 double!”
- 但布局完全不对,因此读取是错误的
✔ reinterpret_cast 的本质:
内存原样不动,只改变你访问它时使用的类型。