C++ 的类型转换分为两类:隐式转换(编译器自动做)和显式转换(程序员主动写)。其中显式转换又分为 C 风格 (T)expr 和 C++ 风格的四种命名转换 static_cast / dynamic_cast / const_cast / reinterpret_cast。

核心原则:能用隐式就别显式;显式里能用 static_cast 解决的,就别用 reinterpret_cast。

一、隐式转换

不需要任何运算符,当一个值赋给另一个兼容类型时会自动发生。

1.1 数值类型自动提升

1
2
3
4
int a = 2;
double d = a; // int → double,自动
char c = 'A';
int x = c; // char → int

1.2 派生类指针 → 基类指针

1
2
3
4
5
6
7
8
class A {};
class B : public A {};

int main() {
B* pSub = new B();
A* pBase = pSub; // 派生类指针自动转基类指针(向上转型,安全)
return 0;
}

向上转型(派生 → 基类)永远是安全的,C++ 默认允许。

1.3 构造函数也能引起隐式转换

如果类 B 有个「接受 A 类型」的构造函数,B b = a; 就会自动调用:

1
2
3
4
5
6
7
8
9
10
11
class A {};
class B {
public:
B(const A& a) {} // 单参数构造函数 → 可被隐式调用
};

int main() {
A a;
B b = a; // 隐式调用 B(const A&)
return 0;
}

容易引发意外。防止办法:构造函数前加 explicit:explicit B(const A& a);,这样只能显式 B b(a); 才能调。

二、显式类型转换(C 风格)

C 风格转换最古老,但也最"暴力"——编译器会按顺序尝试各种转换,直到找到能成功的为止,编译器意图不明确,代码难维护。

1
2
3
int a = 2;
float b = (float)a; // C 风格:数值转换
float c = float(a); // 函数式风格:C++ 也允许

C++ 推荐用下面四种命名转换——意图明确、编译器能查错。

三、C++ 四种命名转换

1
2
3
4
dynamic_cast<目标类型>(表达式)        // 动态转换(运行时检查)
static_cast<目标类型>(表达式) // 静态转换(编译时检查)
reinterpret_cast<目标类型>(表达式) // 重解释转换(位级转换)
const_cast<目标类型>(表达式) // 常量转换(去 / 加 const)

3.1 static_cast:最常用的"静态"转换

编译时检查,不做运行时检查。覆盖大多数"合理"的转换:

  • 数值类型互转(int ↔ double、enum ↔ int);
  • 向上转型(派生 → 基类)和向下转型(基类 → 派生,但不要求多态);
  • void* → 其他类型指针;
  • 调用显式构造函数 / 类型转换运算符。
1
2
3
double d = 3.14;
int n = static_cast<int>(d); // 3
int* p = static_cast<int*>(malloc(sizeof(int)));

几乎所有"能用 static_cast 的场景,都不要用 C 风格转换——意图更明确。

3.2 dynamic_cast:运行时类型检查的向下转型

只能用于指针或引用。主要用来做"多态类的向下转型"——基类指针 → 派生类指针。

要求:基类必须至少有一个虚函数(多态类)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;

class Base {
public:
virtual ~Base() = default; // 多态标识
};
class Derived : public Base {};

int main() {
Base obj; // 注意:obj 真的就是 Base
Derived* p = dynamic_cast<Derived*>(&obj);
if (p == nullptr) cout << "向下转换失败" << endl; // 真的转换失败,返回 nullptr
return 0;
}

如果 Base* 实际指向的是 Derived 对象,转换成功;否则返回 nullptr(指针)或抛 std::bad_cast(引用)。

1
2
3
4
5
6
7
int main() {
Base* obj = new Derived(); // 实际指向 Derived
Derived* p = dynamic_cast<Derived*>(obj);
if (p == nullptr) cout << "向下转换失败";
else cout << "向下转换成功"; // 这次会成功
return 0;
}

为什么需要虚函数? dynamic_cast 运行时检查要查询对象的实际类型,靠虚函数表(vtable) 完成,没虚函数就没有类型信息。

对引用:转换失败会抛 std::bad_cast 异常。

3.3 const_cast:唯一能改 const / volatile 的转换

唯一能添加或去除 const / volatile 属性的转换。被转换的类型必须是指针或引用。

1
2
3
4
5
6
7
8
9
void print(char* str) {
cout << str << endl;
}

int main() {
const char* s = "hello"; // 注意:字符串字面量本身不可写
print(const_cast<char*>(s)); // 仅"骗过"编译器的 const 检查
return 0;
}

⚠️ 不代表被转换的常量真的能被修改。试图修改字符串字面量(位于只读内存)→ 运行时 segment fault。const_cast 的合法用途:对接老旧 C 接口、不修改对象的语义场景。

3.4 reinterpret_cast:位级别强制重解释

最危险。把指针 / 引用的二进制位重新解释成另一种类型,不做任何类型检查。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Base { public: virtual ~Base() = default; };
class Derived : public Base {};
class Other {};

int main() {
Base* p = nullptr;
Derived* p1 = nullptr;
Other other;

p = reinterpret_cast<Derived*>(&other); // 把 Other 当成 Derived
p1 = reinterpret_cast<Derived*>(0xEF); // 把整数 0xEF 当成指针
long long addr = reinterpret_cast<long long>(&other); // 指针转整数

return 0;
}

典型应用场景:

  • 与底层硬件 / 操作系统打交道(如把某块内存当成特定结构体)
  • 网络协议、多媒体字节流处理
  • 自定义内存分配器

⚠️ 除非真的需要操作字节级别,否则永远不要用 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 不能用)。

六、易错点清单

  1. C 风格转换滥用:(T)expr 会按顺序尝试各种转换,意图模糊。全部换成命名转换。
  2. dynamic_cast 用在非多态类:基类没有虚函数,编译直接报错。
  3. dynamic_cast 用在引用上失败抛异常:用指针版本安全,返回 nullptr 就知道失败。
  4. const_cast 改字面量字符串:运行时崩溃;const_cast 只用来"骗过编译器",不代表能改。
  5. reinterpret_cast 当成普通指针转换用:高位指针 / 整数 / 类指针乱转,类型安全 = 0,代码难维护。
  6. 继承向上转型用 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++ 优先使用命名转换,让代码读起来"知道在想什么"。