【C++面试】四种cast转换:static_cast、dynamic_cast、const_cast与reinterpret_cast

一、为什么C++不推荐直接使用C风格强转

C 风格转换:

复制代码
int a = 10;
double b = (double)a;

或者:

复制代码
double b = double(a);

虽然能用,但问题是:

复制代码
它到底进行了什么类型转换
并不够明确

例如:

复制代码
Base* p = (Base*)ptr;

这里可能同时包含:

复制代码
普通类型转换
去除const
指针重新解释
继承体系转换

代码阅读者不容易知道程序员真正的意图。

所以 C++ 提供四种明确的转换:

复制代码
static_cast
dynamic_cast
const_cast
reinterpret_cast

可以先这样记:

复制代码
static_cast
↓
正常、可检查的类型转换

dynamic_cast
↓
多态体系安全向下转型

const_cast
↓
去掉或增加const / volatile

reinterpret_cast
↓
底层地址重新解释

这四个里面,实际开发中最常见的是:

复制代码
static_cast

风险最高、最应该谨慎使用的是:

复制代码
reinterpret_cast

二、static_cast:最常用的正常类型转换

static_cast 主要用于:

复制代码
编译阶段可以确定是否合法

的类型转换。

最典型的就是基本类型转换。

例如:

复制代码
int a = 10;
double b = static_cast<double>(a);      // int转double

或者:

复制代码
double d = 3.14;
int n = static_cast<int>(d);            // double转int,小数部分丢失

也可以进行枚举和整数之间的合理转换。

例如:

复制代码
enum class State {
    Idle = 0,
    Running = 1
};

int value = static_cast<int>(State::Running); // 枚举转int

另外一个非常常见的场景是:

复制代码
继承体系中的上行转换

例如:

复制代码
class Base {};
class Derived : public Base {};

Derived d;
Base* p = static_cast<Base*>(&d);       // Derived* 转 Base*

这种:

复制代码
Derived*
↓
Base*

叫:

复制代码
向上转型
Upcasting

一般是安全的,因为:

复制代码
Derived本身就包含Base部分

很多时候甚至不需要显式写 static_cast:

复制代码
Base* p = &d;

编译器可以自动完成。


static_cast 也可以进行向下转换:

复制代码
Base* p = new Derived;
Derived* d = static_cast<Derived*>(p);

语法上可能允许。

但是问题是:

static_cast 不会在运行时检查 p 实际上是不是 Derived。

例如:

复制代码
Base* p = new Base;
Derived* d = static_cast<Derived*>(p);  // 危险

编译器可能允许转换,但:

复制代码
p实际指向Base对象
并不是Derived对象

后续如果把它当 Derived 使用:

复制代码
可能产生未定义行为

所以:

复制代码
static_cast向下转型
↓
程序员自己保证类型正确

如果不能确定真实类型,应该考虑:

复制代码
dynamic_cast

还有一个非常实用的场景:

复制代码
void* ptr = ...;

如果明确知道它原来就是:

复制代码
int*

可以:

复制代码
int* p = static_cast<int*>(ptr);

因此 static_cast 可以简单理解成:

用于语义上合理、编译器能够检查的常规类型转换,但它不会像 dynamic_cast 那样进行运行时真实类型检查。

面试常见代码:

复制代码
double result = static_cast<double>(a) / b; // 避免整数除法

这其实是非常典型的 static_cast 应用。

三、dynamic_cast:多态体系中的安全向下转型

假设:

复制代码
class Base {
public:
    virtual ~Base() = default;               // 多态基类
};

class Derived : public Base {
public:
    void show() {}
};

现在:

复制代码
Base* p = new Derived;

程序某个地方只拿到了:

复制代码
Base*

但希望判断:

复制代码
它实际是不是Derived对象?

就可以:

复制代码
Derived* d = dynamic_cast<Derived*>(p);

如果:

复制代码
p确实指向Derived

转换成功:

复制代码
d != nullptr

如果:

复制代码
Base* p = new Base;
Derived* d = dynamic_cast<Derived*>(p);

那么转换失败:

复制代码
d == nullptr

所以常见写法:

复制代码
if (Derived* d = dynamic_cast<Derived*>(p)) {
    d->show();                              // 转换成功
} else {
    // p实际不是Derived
}

这就是 dynamic_cast 最大的价值:

复制代码
运行时检查真实对象类型

为什么这里 Base 通常需要:

复制代码
virtual

?

因为 dynamic_cast 在多态向下转换中需要运行时类型信息,也就是:

复制代码
RTTI
Run-Time Type Information

拥有虚函数的类属于:

复制代码
多态类

编译器能够借助运行时类型信息判断:

复制代码
对象真正是什么类型

因此:

复制代码
dynamic_cast

主要和:

复制代码
多态
虚函数
继承
RTTI

联系在一起。


指针转换失败:

复制代码
Derived* d = dynamic_cast<Derived*>(p);

结果是:

复制代码
nullptr

但如果转换的是引用:

复制代码
Derived& d = dynamic_cast<Derived&>(base);

失败时不能返回:

复制代码
nullptr

因为引用不存在"空引用"这种正常返回方式。

所以会抛出:

复制代码
std::bad_cast

可以简单记:

复制代码
dynamic_cast指针失败
↓
nullptr

dynamic_cast引用失败
↓
抛std::bad_cast

面试如果问:

static_cast 和 dynamic_cast 在继承转换上有什么区别?

可以回答:

static_cast 的向下转换主要由程序员自己保证类型正确,不会在运行时检查对象真实类型;dynamic_cast 会利用 RTTI 进行运行时类型检查,失败时指针返回 nullptr,引用抛出 std::bad_cast,因此更安全,但存在一定运行时开销。

四、const_cast:专门修改const或volatile属性

const_cast 最特别的一点就是:

它主要用于增加或去除 const**、** volatile****限定。

例如:

复制代码
const int a = 10;
const int* p = &a;

现在:

复制代码
int* q = const_cast<int*>(p);

这里把:

复制代码
const int*

转换成:

复制代码
int*

但是必须注意:

能转换,不代表可以安全修改。

例如:

复制代码
const int a = 10;
const int* p = &a;

int* q = const_cast<int*>(p);
*q = 20;                                    // 未定义行为

因为:

复制代码
a本身就是一个真正的const对象

你只是通过 const_cast 去掉了指针类型上的 const。

不能因此把真正的 const 对象合法地修改掉。


另一个情况:

复制代码
int a = 10;
const int* p = &a;

int* q = const_cast<int*>(p);
*q = 20;

这里原对象:

复制代码
a

本身不是 const。

只是:

复制代码
p

以 const int* 的方式观察它。

这种情况下去掉 const 后修改:

复制代码
*q = 20;

是可以的。

所以关键在于:

复制代码
原始对象本身是不是const

可以这样记:

复制代码
原对象非const
只是通过const接口访问
↓
const_cast后修改可以合法

原对象本身就是const
↓
const_cast后强行修改
↓
未定义行为

那什么时候实际会用到?

比较典型的场景是:

复制代码
兼容旧的C接口

例如某个旧函数:

复制代码
void legacy_api(char* str);

但你知道:

复制代码
这个函数实际上不会修改str

而手上只有:

复制代码
const char* p;

有时为了兼容接口会写:

复制代码
legacy_api(const_cast<char*>(p));

但这种写法要非常谨慎:

复制代码
必须明确被调用函数确实不会修改这块数据

否则就可能出现问题。


还可以增加 const:

复制代码
int* p = nullptr;
const int* cp = const_cast<const int*>(p);

不过这种:

复制代码
增加const

通常直接隐式转换就能完成:

复制代码
const int* cp = p;

没必要特意使用 const_cast。

所以实际更多见的是:

复制代码
去掉const以适配某些旧接口

五、reinterpret_cast:底层地址重新解释

reinterpret_cast 是四种转换中:

复制代码
最底层
风险最高

的一种。

它的核心思想不是:

复制代码
"把数据转换成另一个类型"

而更像:

复制代码
"把这串二进制地址按照另一种类型重新解释"

例如:

复制代码
int value = 10;
int* p = &value;

可以:

复制代码
std::uintptr_t addr = reinterpret_cast<std::uintptr_t>(p);

把:

复制代码
指针地址

转换成一个足够容纳地址的整数类型。

之后:

复制代码
int* p2 = reinterpret_cast<int*>(addr);

又重新解释回指针。

这种场景在:

复制代码
底层系统编程
硬件接口
序列化框架
某些网络/驱动代码

中可能出现。


还可以在不同指针类型之间转换:

复制代码
int* p = nullptr;
char* c = reinterpret_cast<char*>(p);

它不会帮你:

复制代码
修改底层数据
重新构造对象
检查类型关系

只是:

复制代码
把这个地址当成char*看

所以特别危险。

例如:

复制代码
double d = 3.14;
int* p = reinterpret_cast<int*>(&d);

这不是把:

复制代码
3.14转换成整数3

和:

复制代码
static_cast<int>(d)

完全不是一个概念。

static_cast<int>(d):

复制代码
数值转换

3.14
↓
3

而:

复制代码
reinterpret_cast<int*>(&d)

是:

复制代码
把double对象所在内存地址
当成int*解释

也就是直接碰底层二进制内存。

所以:

复制代码
static_cast
↓
转换"值的语义"

而:

复制代码
reinterpret_cast
↓
重新解释"地址/位模式"

这两个区别非常大。


实际开发中如果普通业务代码里大量出现:

复制代码
reinterpret_cast

通常应该警惕设计是否过于底层或者存在类型安全问题。

它常见于:

复制代码
系统级代码
C接口
网络协议
硬件设备
内存映射
指针与整数地址转换

而不是普通业务逻辑。

六、四种cast怎么快速区分

可以直接用这个表来记:

转换 主要作用 是否运行时检查 常见场景
static_cast 常规类型转换 否 数值转换、向上转型、明确类型转换
dynamic_cast 多态安全转换 是 基类指针/引用向派生类转换
const_cast 修改 cv 限定 否 去除 const/volatile
reinterpret_cast 底层重新解释 否 指针、地址、底层系统开发

最简单的记法:

复制代码
static
↓
普通转换

dynamic
↓
多态转换

const
↓
const转换

reinterpret
↓
地址重新解释

如果面试官问:

四种 cast 分别是什么?

可以这样回答:

static_cast 主要用于编译期可检查的常规类型转换,比如 int 和 double 转换、继承体系的向上转换;dynamic_cast 主要用于多态体系中的安全向下转型,它会在运行时检查真实类型;const_cast 专门用于增加或去除 const、volatile 限定;reinterpret_cast 属于底层重新解释转换,常用于指针和整数地址或者不同指针类型之间的转换,风险最高。

如果继续问:

static_cast 和 C 风格强转有什么区别?

可以回答:

C 风格强转可能隐式组合多种转换方式,语义不明确;static_cast 的意图更加明确,编译器也能进行更严格的类型检查,因此 C++ 中更推荐使用显式的四种 cast。

如果继续问:

dynamic_cast什么时候会失败?

可以回答:

当基类指针实际指向的对象不是目标派生类时,指针形式的 dynamic_cast 返回 nullptr,引用形式会抛出 std::bad_cast。它通常要求源类型是多态类型,也就是至少具有一个虚函数。

如果继续问:

const_cast以后是不是就能修改const对象?

答案是:

不一定。const_cast 只是修改访问类型上的 const 属性,如果原对象本身就是 const,再通过去掉 const 后进行修改属于未定义行为;只有原对象本身并非 const,只是通过 const 指针或引用访问时,去掉 const 后修改才可能合法。

如果继续问:

reinterpret_cast和static_cast有什么本质区别?

可以回答:

static_cast 更偏向语义明确的类型或数值转换,例如 double 转 int;reinterpret_cast 更偏向底层位模式或地址的重新解释,并不会真正把对象语义转换成目标类型,因此类型安全性最低。

最后把四种 cast 串起来:

复制代码
                   C++显式类型转换
                         │
        ┌────────────────┼─────────────────┐
        ↓                ↓                 ↓
   static_cast      dynamic_cast       const_cast
        ↓                ↓                 ↓
    普通转换          多态安全转换       cv属性转换
        │
        │
        └──────────────────────────┐
                                   ↓
                           reinterpret_cast
                                   ↓
                             底层重新解释

实际开发中的使用频率大致可以理解成:

复制代码
static_cast
↓
最常见

dynamic_cast
↓
多态场景使用

const_cast
↓
少量特殊接口使用

reinterpret_cast
↓
底层场景谨慎使用

所以面试时如果记不住细节,至少记住一句:

复制代码
static_cast做正常转换
dynamic_cast做多态安全转换
const_cast改const
reinterpret_cast做底层地址重新解释

再结合一两个代码例子,四种 cast 的核心区别基本就能够说明白。

0voice · GitHub

相关推荐
脉动数据行情11 小时前
Python asyncio 异步实现比特币 BTC 实时行情监听 高并发版
开发语言·python·区块链
爱吃奥利奥_wen1 小时前
排序算法通关手册:从 O(n²) 到 O(n log n),用一篇文章打通算法内功心法
开发语言·经验分享
外收内放1 小时前
Python基础语法练习题(55-56)
开发语言·python
Do_It_Today1 小时前
php 可变函数使用小结
开发语言·php
Do_It_Today1 小时前
PHP 实现数组分页
开发语言·php
怕浪猫1 小时前
多 Agent 系统面试怎么答?我总结了 4 种经典架构
人工智能·算法·面试
weishuangyun11 小时前
小程序制作平台怎么选?三大平台对比!
大数据·开发语言·javascript
我滴老baby1 小时前
Navidrome:飞牛OS安装、曲库导入与远程收听全流程
开发语言·数据库·php
code2cat1 小时前
Java进阶篇之AtomicLongArray:大数累加、上限检查与溢出边界
java·开发语言