C++中的类型转换
前言
C语言中有强制类型转换,C++也有自己设计的一套类型转换。总结来说,C++的类型转换是对C语言的类型转换更细致的种类划分;
C风格的强制类型转换是C++类型转换的总和,在C++中使用C风格的强制类型转换时,编译器会自己挑选合适类型转换(static_cast、reinterpret_cast、const_cast
注:C++的类型转换从文字上看很像模板函数的调用,比如static_cast<int>,但它其实并不是模板函数。它和new/delete一样是C++语法层面上的关键字。所以它的使用不需要#include <stl>
各转换运算符及详细规则
1.static_cast
这应该是最常用转换运算符,因为它的使用规则都是平常在C语言中会用的到且非常符合逻辑的(让人一看就很合理的那种。
✅ 数值 ↔ 数值(
int、double、char等之间的转换)1
2double d = static_cast<double>(5); // int → double
int i = static_cast<int>(5.25); // double → int(截断)✅ 父类指针 ↔ 子类指针(方便多态的使用,合理
1
2
3Derived d;
Base* pb = static_cast<Base*>(&d); // 派生类指针 → 基类指针
Derived* pd = static_cast<Derived*>(pb); // 基类指针 → 派生类指针✅ void ↔ 具体类型指针*(多线程传函数参数会经常用到,合理
1
2void* pv = &d;
Derived* pd2 = static_cast<Derived*>(pv); // void* → 具体类型指针✅ 调用用户定义的转换(构造函数或转换运算符)
1
2
3class MyInt { public: operator int() { return 42; } };
MyInt obj;
int i = static_cast<int>(obj); // 调用 operator int()
2.reinterpret_cast—— “暴力的位重解释”
它的名字就很有意思,reinterpret(重新解释)。更多的是用在无厘头但实际可能有用的场景。
它的转换原理更多的是在位上,也就是说它的转换就类似于暴力的 mov,把内存上的 bit 原样移过去然后按照用户的想法重新解释。
所以需要解释的是reinterpret_cast不可以做数值 ↔ 数值的转换比如(double ↔ int)。因为它的转换是暴力的 mov,首先字节数就不对,其次编码格式不一样,double 是 IEEE浮点数格式,直接暴力的 mov得到的数据完全没有意义。所以(double ↔ int)就需要用 static_cast 解析玩浮点数格式后做数值上的转换。
✅ 指针 ↔ 指针(任意类型,完全不相关也行)
1
2double* pd;
int* pi = reinterpret_cast<int*>(pd); // ✅ 强行把 double 指针当 int 指针用1
2
3
4
5
6
7
8
9
10
11class Entity {};
class Player : public Entity{};
class Enemy : public Entity{};
int main() {
Player* player = new Player;
Entity* entity = static_cast<Entity*>(player); // 有厘头的转换
Enemy* enemy = reinterpret_cast<Enemy*>(player); // 无厘头的转换,只能用reinterpret
return 0;
}✅ 指针 ↔ 整数
1
2int* p = &i;
int addr = reinterpret_cast<int>(p); // ✅ 指针↔整数(保存地址)✅ 数值类型 → 引用(任意类型)
1
2double d = 5.25;
int& ri = reinterpret_cast<int&>(d); // ✅ 强行把 double 的 8 字节当 int 引用
3.const_cast —— “唯一的去 const 工具”
- ✅ 添加或去除
const/volatile限定符(指针或引用)1
2
3
4
5
6const int a = 10;
const int* pca = &a;
int* pa = const_cast<int*>(pca); // ✅ 去除 const
const int& ra = a;
int& r = const_cast<int&>(ra); // ✅ 去除 const
这里乍一看,如果我用pa对a进行了修改怎么办。事实上,语法不会禁止这种操作,运行后a的数据在内存中也确实会被修改,但是在编译时编译器会把所有a用到的地方统统换成了10。所以不会有太大影响
4.dynamic_cast ——“安全的运行时转换”
这个转换的目的只有一个:让子类指针和父类指针能够安全的转换。
能否成功转换的本质:被转换方的值能否成功赋值到目标上。dynamic_cast 做的就是检测它们是否真的是父子关系,如果是则赋值成功,如果不是则赋值为nullptr。
1 | class Entity { |
如何安全的实现转换,重点就在这个动态上。编译器在识别到dynamic_cast会像new一样展开为一段指令,这段指令就用于实现检测entity基类指针指向的对象是否就是enemy子类对象,如果是则正常赋值,如果不是则赋值为nullptr。
1 | Enemy* enemy = dynamic_cast<Enemy*>(entity): |
可以简单看出__dynamic_cast做的就是通过对比typeinfo来实现运行运行时检测。
这也解释了为什么使用dynamic_cast要启用RTTI(run-time type info)。并且父类需要实现虚函数,因为有了虚函数才会有虚函数表,然后对象里才会有存储typeinfo。
dynamic_cast 小技巧
基于__dynamic_cast返回值的原理,dynamic_cast还有一个使用小技巧实现Java那样的运行时类型判断。
1 | Player* p0 = dynamic_cast<Player*>(entity); |
用于实现Java中:
1 | if (entity instanceof Player) { |
