在C++中,有四种类型转换操作符:static_cast, dynamic_cast, const_cast 和 reinterpret_cast。
它们用于不同的场景,提供了更安全的类型转换方式,替代了C语言中简单的强制类型转换(即(type)expression)。
| 转换操作符 | 类型 | 检查时机 | 安全性 | 用途 |
|---|---|---|---|---|
| static_cast | 相关类型 | 编译 | 通常安全 | 基础类型转换,上行转换,显式调用转换函数 |
| dynamic_cast | 多态类型 | 运行 | 安全 | 安全的向下转换和交叉转换 |
| const_cast | const/volatile属性 | 编译 | 不安全 | 添加或移除const/volatile限定符 |
| reinterpret_cast | 任意不相关类型 | 编译 | 不安全 | 底层、硬件相关的比特位重解释 |
为什么需要四种Cast
在C语言中,无论什么类型的转换,都是用 (type)value这种方式。
这样做虽然简便,但是带来了一个问题:转换的意图不清晰。
例如: (int*)ptr这个转换可能意味着:
- 我要去掉const属性
- 我只想把基类指针安全地转成派生类指针
- 我就是要进行一个底层的、重新解释字节的转换
编译器无法区分真实意图。由于C语言缺少像Python等现代语言的运行时环境,这类问题通常会被忽视,一旦代码出错,排查起来非常困难。
1. static_cast
static_cast 是最通用、最常用的转换。
它在编译期进行类型检查,但没有运行时类型检查(RTTI)。它用于编译器认为“在某种程度上合理”的转换。
基本数据类型转换:如 int 转 double,enum 转 int。
- 上行转换(Upcast):将派生类指针/引用转换为基类指针/引用。
- 下行转换(Downcast):将基类指针/引用转换为派生类指针/引用。不安全!编译器在编译期无法知道指针实际指向的是基类对象还是派生类对象。如果指向的不是目标类型,行为未定义(UB)。应该用
dynamic_cast来代替它进行安全的向下转换。 - 任何有转换构造函数或类型转换函数的类类型转换。
示例:
int i = 10;
double d = static_cast<double>(i); // 基本类型转换 int转double
class Base {
};
class Derived : public Base {
};
Derived der;
Base* basePtr = static_cast<Base*>(&der); // 上行转换
Base base;
Derived* derPtr = static_cast<Derived*>(&base); // 下行转换,运行时可能出错。
void* vPtr = &i;
int* iPtr = static_cast<int*>(vPtr); // 将void*转回原始类型2. dynamic_cast
dynamic_cast被称为动态转换,用于安全的沿继承链的向下或交叉转换。
专门用于有虚函数的继承体系(多态类型)中的指针或引用转换。它会在运行时进行类型检查,因此有性能开销。
安全的向下转换:检查一个基类指针是否真正指向一个派生类对象。如果是,转换成功;否则,对于指针返回nullptr,对于引用抛出std::bad_cast异常。
它需要运行时类型信息(RTTI),所以基类必须至少有一个虚函数。
示例:
class Base {
virtual void dummy() {
}
};
class Derived : public Base {
};
Base* basePtr = new Derived; // 基类指针实际指向派生类对象
// 向下转换
Derived* derPtr = dynamic_cast<Derived*>(basePtr);
if (derPtr) {
// 转换成功,basePtr确实指向Derived对象
} else {
// 转换失败,basePtr指向的不是Derived对象
}
Base actualBase;
Derived* badPtr = dynamic_cast<Derived*>(&actualBase); // 失败,badPtr将是nullptr
// 引用
try {
Derived& derRef = dynamic_cast<Derived&>(*basePtr);
} catch (const std::bad_cast& e) {
// 转换失败
}3. const_cast
const_cast 用于处理那些原本不是const但却被声明为const的变量,或者调用一些老旧的不支持const正确性的库函数。
尝试修改一个原本被声明为const的对象,会导致未定义行为,在工程中,应尽量避免使用该转换。
你只能用const_cast去除“指向const的指针”的constness,而不能修改一个真正的常量。
示例:
// 合法且安全的使用场景
void print(char* str) { // 一个老旧的、不接收const char*的函数
cout << str << endl;
}
const char* greeting = "Hello";
// print(greeting); // 错误!不能将const char*传递给char*
print(const_cast<char*>(greeting)); // 正确,移除了const
// 尝试修改const常量 (不要这么做)
const int i = 42;
const int* constPtr = &i;
int* modifiable = const_cast<int*>(constPtr);
*modifiable = 100; // 未定义行为!因为i本身是一个常量。4. reinterpret_cast
reinterpret_cast 提供了底层的、基于比特位的重新解释。用于完全不相关类型之间的转换,通常在低级编程中,如驱动、序列化、网络协议等。
- 指针和整数之间的转换(如将指针地址转成一个uintptr_t)。
- 一种类型的指针转换为另一种完全不相关类型的指针(如Foo转Bar)。
这是最危险的转换。使用它极易导致未定义行为,并且代码不可移植。
它的结果通常需要再转回原始类型后才能使用。
示例:
int i = 0x11451419;
int* iPtr = &i;
// 将int*强制解释为char*,用于检查内存字节
char* charPtr = reinterpret_cast<char*>(iPtr);
for (size_t n = 0; n < sizeof(int); ++n) {
cout << static_cast<int>(charPtr[n]) << " "; // 输出内存中的每个字节
}
// 函数指针之间的转换
typedef void (*FuncPtr)();
FuncPtr funcPtr = reinterpret_cast<FuncPtr>(someFunction); // 假设someFunction签名不同
// 指针到整数(地址值)
uintptr_t addr = reinterpret_cast<uintptr_t>(iPtr);
最新评论