在 C++ 提供的 4 种专用类型转换运算符中,reinterpret_cast(重新解释转换)是最偏向底层的一个。从英文拼写上就很容易记住:reinterpret 由 “re(重新)” 和 “interpret(解释)” 组合而成,意思是对同一块内存重新解释。
它的工作方式跟前面学过的 static_cast 完全不同:static_cast<double>(65) 会按照数值规则算出一个新的浮点数 65.0;而 reinterpret_cast 不会改变内存里原有的二进制数据,只是换一种指针或引用类型去重新看待这块内存。
在实际开发中,普通的业务代码很少会直接用到它,小伙伴们不需要有心理负担。它主要出现在二进制文件读写、网络通信协议或底层硬件接口等场景中。
基本语法与常见用法
当我们需要按单字节读取某个变量的底层内存,或者把指针保存的内存地址转换成整数时,可以使用 reinterpret_cast 来完成转换。
语法:
reinterpret_cast<目标类型>(表达式)说明:
其中,尖括号 <> 里的目标类型与圆括号 () 里的表达式,主要用于以下 3 类底层转换:① 指针与指针之间;② 引用与引用之间;③ 指针与足够大的整数类型之间。下面我们来看看最常见的两种用法。
1. 转为 char* 按字节读取内存
读写二进制文件或收发网络数据时,底层接口常常要求我们把普通变量的地址当成 “字节序列” 传进去。这时就可以通过 reinterpret_cast 将普通指针转换为 char* 或 unsigned char*。
示例 1:转为 char* 字节指针
#include <iostream>
int main() {
int num = 65;
// 将 int* 重新解释为 char* 字节指针
char* bytePtr = reinterpret_cast<char*>(&num);
std::cout << num << '\n';
std::cout << *bytePtr << '\n';
return 0;
}运行结果如下。
65
A分析:
reinterpret_cast<char*>(&num) 表示把整型变量 num 的地址当成 char* 字节指针来使用。在日常使用的主流电脑中,多字节整数采用低位字节排在前面的存储顺序,因此正整数 65 刚好保存在起始的第 1 个字节里,后面 3 个字节均为 0。
在转换过程中,内存里的二进制数据没有任何改变,只是指针 bytePtr 把起始的第 1 个字节当成了 char 类型来读取。由于 65 在 ASCII 码表中对应的正好是大写字母 'A',因此 *bytePtr 输出了字符 'A'。
这里大家要区分清楚:如果平时只是想把整数 65 转为字符 'A',直接使用 static_cast<char>(65) 就可以了。这里转为 char* 只是为了演示按单字节读取底层内存,这也是 C++ 标准中明确允许的合法操作。
2. 指针与整数地址互转
对接操作系统或底层硬件接口时,有时还需要我们把指针保存的内存地址转换成整数来传递。C++ 在 <cstdint> 头文件中提供了 std::uintptr_t 类型(由 unsigned、int、ptr 缩写而成),专门用来保存指针的整数地址。
示例 2:指针与整数地址互转
#include <cstdint> // 提供 std::uintptr_t
#include <iostream>
int main() {
int num = 100;
int* ptr = #
// 将指针转换为整数地址
std::uintptr_t addr = reinterpret_cast<std::uintptr_t>(ptr);
std::cout << "整数地址:" << addr << '\n';
// 将整数地址还原为 int* 指针
int* restoredPtr = reinterpret_cast<int*>(addr);
std::cout << "还原后的值:" << *restoredPtr << '\n';
return 0;
}运行结果如下。
整数地址:140732898695068
还原后的值:100分析:
在这个例子中,我们先使用 reinterpret_cast<std::uintptr_t>(ptr) 把指针 ptr 保存的地址转成无符号整数 addr。平时直接打印指针看到的是十六进制地址,这里输出的则是它对应的十进制数值,每次运行程序时数值都会随内存位置变化。
随后,我们又通过 reinterpret_cast<int*>(addr) 将该整数还原回 int* 指针。整个过程只是让同一个地址在 “指针形式” 和 “整数形式” 之间往返了一趟,并没有改动变量 num,因此解引用 *restoredPtr 依然输出 100。
可能有些小伙伴会问:“既然都是转成整数,为什么不直接转成普通的 int 类型呢?” 这是因为在如今的 64 位系统中,一个指针占 8 个字节,而普通的 int 通常只占 4 个字节,根本装不下完整的地址。
如果我们强行写成 reinterpret_cast<int>(ptr),不仅会截断地址,编译器还会直接报错。而 std::uintptr_t 的宽度会自动跟随当前系统的指针大小变化,能够完整装下整个内存地址。
reinterpret_cast 的使用限制
reinterpret_cast 的语法虽然简单,但它是 C++ 中最底层、也最容易误用的转换工具。在使用时,我们需要清楚以下 3 点限制:
- 不能用于普通数值之间的转换:
reinterpret_cast只处理指针、引用或整数地址表示,不能代替static_cast把double转换为int。 - 不能去掉指针或引用的 const 限定:即使是重新解释底层指针类型,
reinterpret_cast也不能去除const只读属性。如果我们需要去掉只读限定,必须使用专门的 const_cast。 - 无关类型指针强转后不能随意解引用:把
int*强转成double*虽然能通过编译,但两者占用的字节数与存储格式完全不同。一旦我们直接解引用该double*指针,就会导致内存越界访问甚至程序崩溃。
在下面这几种错误写法中,前两种会在编译阶段直接报错,最后一种虽然能编译通过,但在运行时解引用是十分危险的操作。
double price = 9.8;
const int limit = 100;
int num = 10;
int a = reinterpret_cast<int>(price); // 报错:非指针转换
char* p = reinterpret_cast<char*>(&limit); // 报错:不能去除 const
double* dp = reinterpret_cast<double*>(&num);
std::cout << *dp << '\n'; // 危险:越界读取内存4 种类型转换运算符对比
学到这里,C++ 提供的 4 种专用类型转换运算符我们就全部认识了。它们各自有着明确的职责边界,具体区别如下表所示。
| 转换运算符 | 主要用途 | 常见场景 |
|---|---|---|
| static_cast | 编译期已知安全的关联类型转换 | 基础数值互转、enum class 转整数、还原 void* |
| dynamic_cast | 多态继承体系的运行时安全转换 | 基类指针或引用安全向下转换为派生类 |
| const_cast | 调整指针或引用上的 const 限定 | 原变量非 const 时恢复修改权、对接只读旧接口 |
| reinterpret_cast | 底层内存与地址的重新解释 | 转为 char* 按字节读写、指针与 uintptr_t 互转 |
在实际开发中,90% 以上的显式转换使用的都是 static_cast。只有涉及多态运行时向下检查时,我们才选用 dynamic_cast。涉及去除只读限定或底层地址重解释时,再按上表分工选用 const_cast 或 reinterpret_cast。
提示 遇到类型不匹配的编译报错时,小伙伴们千万不要 “挨个换一种 cast 试试,直到编译通过为止”。先看清两边是什么类型,再按各自的职责选对转换工具,才能避开底层的内存隐患。
