面试cast:reinterpret_cast/const_cast/static_cast/dynamic_cast

目录

1. cast

2. reinterpret_cast 

3. const_cast

3.1 加上const的情况

3.2 去掉const的情况

4. static_cast

4.1 基本类型之间的转换

4.2 void指针转换为任意基本类型的指针

4.3 子类和父类之间的转换

5. dynamic_cast

5.1 RTTI(Run-time Type Identification)


1. cast

英 /kɑːst/ 美 /kæst/

v. 铸造;投(钓线);投票;投射(光、影子等);扔;使人怀疑;向…投以(视线、笑容等);分配角色;(蛇)蜕(皮);造谣中伤;踢落;把某人描写成

n. 铸件;铸模;特性;模子;铸造品;(一出戏剧或一部电影的)全体演员

在C++程序里是一种转型机制,跟物理铸造差不多,有一个模子(如int),然后根据这个模子生成一个铸件。

double a = 1.1;
char* b = reinterpret_cast<char*>(&a);
// char*就是一个新的模具,double指针是原料,通过铸造case变成一个新的铸件b

2. reinterpret_cast 

reinterpret

英 /ˌriːɪnˈtɜːprət/,  美 /ˌriːɪnˈtɜːrprət/

vt. 重新解释;重新诠释

reinterpret_cast是四种强制转换中功能最为强大的(最暴力,最底层,最不安全),跟它的英文释义一样重新诠释。它的本质是编译器的指令。

它的作用:它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针。或者不同类型的指针的相互替换

代码示例:

#include <iostream>
int main()
{
    double a = 1.1;
    char* b = reinterpret_cast<char*>(&a);
    double* c = reinterpret_cast<double*>(b);
    printf("a:%lf, %p\n", a, &a);
    printf("b:%c, %p\n", *b, b);
    printf("c:%lf, %p\n", *c, c);
}

输出:

我尝试打断点进入reinterpret_cast内部,发现并不行(它的本质是编译器的指令)。

通过中间的 char*来转double*,但是没有出现精度问题。事实上reinterpret_cast只是在编译器进行的予以转化(并未拷贝,只是重新诠释),只要是个地址就可以转(二进制拷贝)。

3. const_cast

 有两个功能,去掉const和加上const。

3.1 加上const的情况

#include <iostream>
int main()
{
    int* a = new int(1);
    const int* b = const_cast<const int*>(a);
    *a = 2;
    //*b=2;,常量不能修改
    printf("0x%p,%d\n", a,*a);
    printf("0x%p,%d\n", b,*b);
    return 0;
}

输出:

 

发现值是一样的,但是地址也是一样,说白了加const就是加了一个修饰和限定

3.2 去掉const的情况

#include <iostream>
class A
{
public:
    int num;
    A(int val = 100) :num(val) {}
    ~A() {}
};
int main()
{
    //1.const 修饰指针对象,指向原对象
    const A* pa1 = new A(200);
    A* cast_pa1 = const_cast<A*>(pa1);
    printf("1.const 修饰指针指向对象,指向原对象\n");
    printf("%p\n", pa1);
    printf("%p\n", cast_pa1);

    //2.const 修饰指向指针对象的值,指向原对象
    A* const pa2 = new A(200);
    A* cast_pa2 = const_cast<A*>(pa2);
    printf("2.const 修饰指向对象的值,指向原对象\n");
    printf("%p\n", pa2);
    printf("%p\n", cast_pa2);

    //3.const 同时修饰指针对象和指针对象的值,指向原对象
    const A* const pa3 = new A(200);
    A* cast_pa3_1 = const_cast<A*>(pa3);
    const A* cast_pa3_2 = const_cast<A*>(pa3);
    A* const cast_pa3_3 = const_cast<A*>(pa3);
    printf("3.const 同时修饰指针对象和指针对象的值,指向原对象\n");
    printf("%p\n", pa3);
    printf("%p\n", cast_pa3_1);
    printf("%p\n", cast_pa3_2);
    printf("%p\n", cast_pa3_3);

    //4.const 修饰普通对象,并且赋值给一般对象,不指向原对象
    const A pa4;
    A cast_pa4 = const_cast<A&>(pa4);
    printf("4.const 修饰普通对象,并且赋值给一般对象\n");
    printf("%p\n", &pa4);
    printf("%p\n", &cast_pa4);

    //5.const 修饰普通对象,并且赋值给引用对象,指向原对象
    const A pa5;
    A& cast_pa5 = const_cast<A&>(pa5);
    printf("5.const 修饰普通对象,并且赋值给引用对象\n");
    printf("%p\n", &pa5);
    printf("%p\n", &cast_pa5);

    // 6. const 修饰对象,对象指针去 const 属性后赋给指针,指向原对象
    const A pa6;
    A* cast_pa6 = const_cast<A*>(&pa6);
    printf("6. const 修饰对象,对象指针去 const 属性后赋给指针\n");
    printf("%p\n", &pa6);
    printf("%p\n", cast_pa6);

    //7.const修饰局部变量,不指向原对象
    const int pa7 = 1;
    int  cast_pa7_1 = const_cast<int&>(pa7);
    int& cast_pa7_2 = const_cast<int&>(pa7);
    int* cast_pa7_3 = const_cast<int*>(&pa7);

    printf("6. const 修饰对象,对象指针去 const 属性后赋给指针\n");
    printf("地址,  pa7:%p\n", &pa7);
    printf("cast_pa7_1:%p\n", &cast_pa7_1);
    printf("cast_pa7_2:%p\n", &cast_pa7_2);
    printf("cast_pa7_3:%p\n", cast_pa7_3);
    cast_pa7_1 = 10;
    printf("pa7:%d,未修改\n", pa7);
    printf("cast_pa7_1:%d\n", cast_pa7_1);
    cast_pa7_2 = 100;
    printf("pa7:%d,未修改\n", pa7);
    printf("cast_pa7_1:%d\n", cast_pa7_1);
    printf("cast_pa7_2:%d\n", cast_pa7_2);
    *cast_pa7_3 = 1000;
    printf("pa7:%d,未修改\n", pa7);
    printf("cast_pa7_1:%d\n", cast_pa7_1);
    printf("cast_pa7_2:%d\n", cast_pa7_2);
    printf("cast_pa7_3:%d\n", *cast_pa7_3);
    return 0;
}

输出

1.const 修饰指针指向对象,指向原对象
016E7820
016E7820
2.const 修饰指向对象的值,指向原对象
016E77C0
016E77C0
3.const 同时修饰指针对象和指针对象的值,指向原对象
016E7850
016E7850
016E7850
016E7850
4.const 修饰普通对象,并且赋值给一般对象
012FFD24
012FFD18
5.const 修饰普通对象,并且赋值给引用对象
012FFD0C
012FFD0C
6. const 修饰对象,对象指针去 const 属性后赋给指针
012FFCF4
012FFCF4
6. const 修饰对象,对象指针去 const 属性后赋给指针
地址,  pa7:012FFCDC
cast_pa7_1:012FFCD0
cast_pa7_2:012FFCDC
cast_pa7_3:012FFCDC
pa7:1,未修改
cast_pa7_1:10
pa7:1,未修改
cast_pa7_1:10
cast_pa7_2:100
pa7:1,未修改
cast_pa7_1:10
cast_pa7_2:1000
cast_pa7_3:1000

分析:

指针之间的转换无论怎样还是原地址

去掉一般对象的const,如果赋值给一般对象则是新对象(A cast_pa4 = const_cast<A &>(pa4);)

去掉内置类型(如int)变量的const,如果赋值给一般对象则是新对象,否则全是原来对象地址(虽然地址是一样的,但是值是不一样的)疑惑中。。。。。。

4. static_cast

 三个作用:

1.基本类型之间的转换

2.void指针转换为任意基本类型的指针,基本类型指针之间无法使用

3.用于有继承关系的子类与父类之间的指针或引用的转换

4.1 基本类型之间的转换

#include <iostream>
int main()
{
    double i = 1.1;
    int a = static_cast<int>(i);
    double b = static_cast<double>(a);
    int c = static_cast<int>(b);
    printf("i:%lf, %p\n", i, &i);
    printf("a:%d, %p\n", a, &a);
    printf("b:%lf,%p\n", b, &b);
    printf("c:%d, %p\n", c, &c);
}

输出

可以进行基本类型的转化,但是会损失精度类似与C语言的强制转化。转换过程实际上是有内存拷贝的,每次地址都不一样。跟reinterpret_cast不太一样reinterpret_cast是底层二进制的强制拷贝和语义转换不会损失精度。

注:reinterpret_cast不能进行基本类型之间的转换,只能做指针转换。

4.2 void指针转换为任意基本类型的指针

#include <iostream>
int main()
{
    double a = 1.1;
    void* b = static_cast<void*>(&a);
    double* c = static_cast<double*>(b);
    //int* d = static_cast<int*>(c); // 类型转换无效(基本类型指针之间无法使用)
    printf("a:%lf, %p\n", a, &a);
    printf("b:%p\n", b);
    printf("c:%lf, %p\n", *c, c);
}

这里是void指针和其他类型的指针进行的转化,结果是指向的是原地址,跟reinterpret_cast是一样的结果。说白了static_cast就是能做铁水变铁器和铁器变铁水,但是不能直接把铁镰刀变铁锄头。reinterpret_cast就是可以指鹿为马,能直接把铁镰刀变铁锄头,但是变成的铁锄头能不能刨地就不知道了。这里需要区分普通类型之间的转换和普通类型指针之间的转换(普通类型的转换不是)。

//int* d = static_cast<int*>(c); // 类型转换无效(基本类型指针之间无法使用

4.3 子类和父类之间的转换

#include <iostream>
using namespace std;
class A
{
    public:
    A(){};
    void foo()
    {
        cout<<"A!"<<endl;
    }
};
class B:public A
{
    public:
    B(){} ;
    void foo()
    {
        cout<<"B!"<<endl;
    }
};
int main()
{
    A *a = new A();
    B * b = static_cast<B *>(a);
    b->foo();
    return 0;
}

输出: 

这是向下转型,是不安全的,但是为什么没有报错呢,因为B中还没有B特有的(B的成员变量),A和B两者在内存中的结构是一致的。

举个不一致的例子:


#include <iostream>
using namespace std;
class A
{
    public:
    A(){}
    void foo()
    {
        cout<<"A!"<<endl;
    }
};
class B:public A
{
    char b='c';
    public:
    B(){}
    void foo()
    {
        cout<<b<<endl;
    }
};
int main()
{
    A* a = new A();
    a->foo();
    B* b = static_cast<B*>(a);
    b->foo();
    B* pb = new B();
    pb->foo();
    return 0;
}

输出 

分析:这里就发生了错误了,B中特有的成员变量没有初始化(使用了不安全的向下转型)

5. dynamic_cast

dynamic_cast用于类继承层次间指针或引用转换(主要用于向下的安全转换)

dynamic_cast向下转型的安全性主要体现在RTTI

5.1 RTTI(Run-time Type Identification)

运行时类型识别。程序能够使用基类的指针或引用来检查着这些指针或引用所指的对象的实际派生类型(判断指针原型)

RTTI提供了两个非常有用的操作符:typeid和dynamic_cast。(三个最主要的东西,dynamic_cast,typeid,type_info)

typeid:typeid函数(为type_info类的友元函数,为什么要这样呢?目的是防止创建type_info对象)的主要作用就是让用户知道当前的变量是什么类型的,它可以返回一个type_info的引用,可以获取类的名称和编码typeid重载了type_info中的==和!=可以用于判断两个类型是否相等

1)typeid识别静态类型

当typeid中的操作数是如下情况之一时,typeid运算符指出操作数的静态类型,即编译时的类型。

(1)类型名

(2)一个基本类型的变量

(3)一个具体的对象(非指针对象)

(4)一个指向 不含有virtual函数的类 对象的指针的解引用

(5)一个指向 不含有virtual函数的类 对象的引用

静态类型在程序的运行过程中并不会改变,所以并不需要在程序运行时计算类型,在编译时就能根据操作数的静态类型,推导出其类型信息。例如如下的代码片断,typeid中的操作数均为静态类型

#include <iostream>
#include <typeinfo>
using namespace std;
class X {
public:
    X() {   };
    virtual void func() = 0;
};
class XX : public X {
public:
    XX() { };
    void func() { };
};
class Y {
public:
    Y() {  };
    void func() { };
};
int main()
{
    int n = 0;
    XX xx;
    Y y;
    Y* py = &y;
    X* px = &xx;
    // int和XX都是类型名
    cout << typeid(int).name() << endl;
    cout << typeid(XX).name() << endl;
    // n为基本变量
    cout << typeid(n).name() << endl;
    // xx所属的类虽然存在virtual,但是xx为一个具体的对象
    cout << typeid(xx).name() << endl;
    // py为一个指针,属于基本类型
    cout << typeid(py).name() << endl;
    // py指向的Y的对象,但是类Y不存在virtual函数
    cout << typeid(*py).name() << endl;
    // px
    cout << typeid(*px).name() << endl;
    cout << typeid(px).name() << endl;
    return 0;
}

输出: 

对于px,px是 class X *,但是*px是class XX。

动态类型转换:

#include <iostream>
#include <typeinfo>
using namespace std;
class X
{
public:
    X() { mX = 101;}
    virtual ~X(){}
private:
    int mX;
};

class XX : public X
{
public:
    XX() :X() { mXX = 1001; }
    virtual ~XX() { }
private:
    int mXX;
};

class YX : public X
{
public:
    YX() { mYX = 1002; }
    virtual ~YX() { }
private:
    int mYX;
};
int main()
{
    X x;
    XX xx;
    YX yx;
    // 子类直接转父类指针,没问题
    X* px = &xx;
    cout << "px:\t" << px << endl;
    XX* pxx = dynamic_cast<XX*>(px); // 转换1,成功
    cout << "pxx:\t" << pxx << endl;
    YX* pyx = dynamic_cast<YX*>(px); // 转换2,失败
    cout << "pyx:\t" << pyx << endl;
    pyx = (YX*)px;                   // 转换3,成功
    cout << "pyx:\t" << pyx << endl;
    pyx = static_cast<YX*>(px);      // 转换4,成功
    cout << "pyx:\t" << pyx << endl;
    return 0;
}

输出:

px是一个基类(X)的指针,但是它指向了派生类XX的一个对象。在转换1中,转换成功,因为px指向的对象确实为XX的对象。在转换2中,转换失败,因为px指向的对象并不是一个YX对象,此时dymanic_cast返回nullptr。转换3为C风格的类型转换而转换4使用的是C++中的静态类型转换,它们均能成功转换,但是这个对象实际上并不是一个YX的对象,所以在转换3和转换4中,若继续通过指针使用该对象必然会导致错误,所以这个转换是不安全的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/187836.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

kafka 集群 KRaft 模式搭建

Apache Kafka是一个开源分布式事件流平台&#xff0c;被数千家公司用于高性能数据管道、流分析、数据集成和关键任务应用程序 Kafka 官网&#xff1a;https://kafka.apache.org/ Kafka 在2.8版本之后&#xff0c;移除了对Zookeeper的依赖&#xff0c;将依赖于ZooKeeper的控制器…

【JavaEE初阶】线程安全问题及解决方法

目录 一、多线程带来的风险-线程安全 1、观察线程不安全 2、线程安全的概念 3、线程不安全的原因 4、解决之前的线程不安全问题 5、synchronized 关键字 - 监视器锁 monitor lock 5.1 synchronized 的特性 5.2 synchronized 使用示例 5.3 Java 标准库中的线程安全类…

vscode运行c++程序如何支持c++11

参考https://zhuanlan.zhihu.com/p/269244754 更改setting.json文件

Spring cloud - Feign

Feign的作用 Feign是Netflix公司开发的声明式web客户端组件&#xff0c;Spring对Feign做了无缝集成&#xff1a; Feign is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has plugg…

什么是 Jest ? Vue2 如何使用 Jest 进行单元测试?Vue2 使用 Jest 开发单元测试实例

什么是Jest? Jest 是一个流行的 JavaScript 测试框架,由 Facebook 开发并维护,专注于简单性和速度。它通常用于编写 JavaScript 和 TypeScript 应用程序的单元测试、集成测试和端到端测试。 特点: 简单易用: Jest 提供简洁的 API 和易于理解的语法,使得编写测试用例变得…

kolla-ansible 部署OpenStack云计算平台

目录 一、环境 二、安装及部署 三、测试 一、环境 官方文档&#xff1a;https://docs.openstack.org/kolla-ansible/yoga/user/quickstart.html rhel8.6 网络设置&#xff1a; 修改网卡名称 网络IP&#xff1a; 主机名&#xff1a; 网络时间协议 配置软件仓库 vim docke…

模拟退火算法应用——求解二元函数的最小值(极小值)

仅作自己学习使用 一、问题 二、代码 clear clcT1 cputime; xmax 5; xmin -5; ymax 5; ymin -5; L 20; % 马尔科夫链长度 dt 0.998; % 降温系数 S 0.02; % 步长因子 T 200; % 初始温度 TZ 1e-8; % 容差 Tmin 0.01;% 最低温度 P 0; % Metropolis接受…

持续集成交付CICD:GitLabCI 通过trigger触发流水线

目录 一、理论 1.GitLabCI 二、实验 1.搭建共享库项目 2.GitLabCI 通过trigger触发流水线 三、问题 1.项目app02未触发项目app01 2.GitLab 报502网关错误 一、理论 1.GitLabCI (1) 概念 GitLab CI&#xff08;Continuous Integration&#xff09;是一种持续集成工具…

为什么要隐藏id地址?使用IP代理技术可以实现吗?

随着网络技术的不断发展&#xff0c;越来越多的人开始意识到保护个人隐私的重要性。其中&#xff0c;隐藏自己的IP地址已经成为了一种常见的保护措施。那么&#xff0c;为什么要隐藏IP地址&#xff1f;使用IP代理技术可以实现吗&#xff1f;下面就一起来探讨这些问题。 首先&am…

蓝桥杯每日一题2023.11.24

题目描述 #include <stdio.h> #define N 100int connected(int* m, int p, int q) {return m[p]m[q]? 1 : 0; }void link(int* m, int p, int q) {int i;if(connected(m,p,q)) return;int pID m[p];int qID m[q];for(i0; i<N; i) ________________________________…

没搞懂二维差分是什么怎么办???

摸鱼的时候画的&#xff0c;根据公式反推 一维差分倒是懂了 a[10]{1,2,6,9,11,12,17,21,32,67}; c[10]{1,1,4,3,2,1,5,4,11,35}; 现要把[3,7]的值都增加3 c[10]{1,1,7,3,2,1,5,1,11,35}; 要查询的时候再用for循环相加 结论&#xff1a;成立且适用于多次修改 不知道为什么这个…

跨域攻击分析和防御(上)

点击星标&#xff0c;即时接收最新推文 跨域攻击 在大型公司或大型跨国企业中都拥有自己的内网&#xff0c;跨国公司都有各个子公司一般以建立域林进行共享资源。根据不同职能区分的部门&#xff0c;从逻辑上以主域和子域进行划分以方便统一管理。在物理层使用防火墙将各个子公…

『亚马逊云科技产品测评』活动征文|EC2 实例安装 docker 与配套软件部署前后端分离的医疗管理后台系统

授权声明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 Developer Centre, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道 目录 一、AWS 产品类别选择 &#xff08;1&#xff09;应用服务器选择…

浅谈现代化城市建设中智慧消防的研究与应用

安科瑞 华楠 【摘要】随着城市现代化发展&#xff0c;城市居住密度愈来愈大&#xff0c;城市建筑结构复杂多样化&#xff0c;高层建筑火灾发生率在不断地升高。对现代化城市面临的消防问题展开讨论&#xff0c;针对智慧消防在现代化城市建设中的现状进行了分析&#xff0c;并提…

探索 Vue 中的 bus.$emit:实现组件通信的强大工具

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

快手AI布局:从直播电商到大模型,如何打造智能生态?

快手科技在2023年第三季度业绩中&#xff0c;首次披露了关于AI业务的一些重要信息&#xff0c;显示出其对AI的重视和投入。快手AI的核心业务和竞争优势是什么&#xff1f;AI的发展&#xff0c;对快手业绩带来了哪些方面的提振&#xff1f; 快手AI业务板块&#xff1a;直播电商…

华为云人工智能入门级开发者认证学习笔记

人工智能入门级开发者认证 人工智能定义 定义 人工智能 (Artificial Intelligence) 是研究、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。 强人工智能 vs 弱人工智能 强人工智能&#xff1a;强人工智能观点认为有可能制造出真正能推理&#xff08…

【React】打包体积分析 source-map-explorer

通过分析打包体积&#xff0c;才能知道项目中的哪部分内容体积过大&#xff0c;方便知道哪些包需要进一步优化。 使用步骤 安装分析打包体积的包&#xff1a;npm i source-map-explorer在 package.json 中的 scripts 标签中&#xff0c;添加分析打包体积的命令对项目打包&…

【Java并发】聊聊创建线程池的几种方式以及实际生产如何应用

上一篇文章&#xff0c;主要讲述了如果通过线程池进行执行任务&#xff0c;以及相关的核心流程&#xff0c;线程执行框架本身提供了一系列的类&#xff0c;封装了线程创建、关闭、执行、管理等跟业务逻辑无关的代码逻辑&#xff0c;一方面将业务和非业务逻辑进行解耦合&#xf…

Docker简介与安装

Docker简介 1.什么是docker docker是解决了运行环境和配置问题的软件容器。是一种方便做持续集成并有助于整体发布的容器虚拟化技术。docker官网&#xff1a;http://www.docker.com 2.与传统虚拟技术的区别 传统虚拟机技术是虚拟出一套硬件后&#xff0c;在其上运行一个完整…
最新文章