面试cast:reinterpret_cast/const_cast/static_cast/dynamic_cast

news2024/9/23 11:27:51

目录

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.coloradmin.cn/o/1244478.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SQLY优化

insert优化 1.批量插入 手动事务提交 主键顺序插入&#xff0c;主键顺序插入性能高于乱序插入 2.大批量插入数据 如果一次性需要插入大批量数据&#xff0c;使用Insert语句插入性能较低&#xff0c;此时可以使用MYSQL数据库提供的load指令进行插入 主键优化 主键设计原则 …

计算机基础知识57

前后端数据传输的编码格式(contentType) # 我们只研究post请求方式的编码格式&#xff1a; get请求方式没有编码格式-- index?useranme&password get请求方式没有请求体&#xff0c;参数直接在url地址的后面拼接着 # 有哪些方式可以提交post请求&#xff1a;f…

SAP GOS与DMS简介

通常在项目实施过程中很多业务数据需要管理对应的系统外的附件&#xff0c; 制造业的BOM需要对应图纸&#xff0c;采购订单需要对应线下的采购合同&#xff0c;物料需要对应相应的参数文件等等&#xff0c;很多产品都会遇到业务数据和系统外相关资料的关联&#xff0c;有PDF的文…

2002-2020年341个地级市农业保险收入数据

2002-2020年341个地级市农业保险收入数据 1、时间&#xff1a;2002-2020年 2、范围&#xff1a;341个地级市 3、指标&#xff1a;农业保险收入 4、来源&#xff1a;整理自wind、保险年鉴 5、指标解释&#xff1a; 农业保险保费收入是指保险公司从农户或农业生产经营者那里…

聊一聊Linux动态链接和GOT、PLT

共享动态库是现代系统的一个重要组成部分&#xff0c;大家肯定都不陌生&#xff0c;但是通常对背后的一些细节上的实现机制了解得不够深入。当然&#xff0c;网上有很多关于这方面的文章。希望这篇文章能够以一种与其他文章不同的角度呈现&#xff0c;可以对你产生一点启发。 …

@Scheduled注解 定时任务讲解

用于在Java Spring框架中定时执行特定任务的注解 Scheduled&#xff0c;它能够指定方法在特定时间间隔或特定时间点执行。默认参数是cron&#xff0c;cron参数被用来定义一个Cron表达式&#xff0c;它代表了任务执行的时间规则 参数如下 Cron 这是是一种时间表达式&#xff…

Python版本切换的解决方法,升级Python 2.7 到 Python 3.7

之前解决问题的时候&#xff0c;记录的一个Python版本切换的解决方法&#xff0c;今天在解决其他问题的时候&#xff0c;查看了下之前写的这个解决方法。还是很适用的。然后加入了下linux系统中python配置修改的问题。 Mac 升级Python 2.7 到 Python 3.7 1、python3.7官网下载…

【SpringBoot】 This application has no explicit mapping for 解决方法

This application has no explicit mapping for 解决方法 This application has no explicit mapping for 解决方法一、背景二、原因三、解决方案方式一&#xff1a;方式二&#xff1a; 四、解决 This application has no explicit mapping for 解决方法 一、背景 在SpringBo…

EXCEL小技巧

1、两列文本合并显示&#xff1a; CONCATENATE(B6,E6) &#xff08;如果显示公式而非文本&#xff0c;就是公式输错了&#xff0c;比如后缺少空格&#xff09;

Word中如何实现 图片 | 表格 自动编号与文中引用编号对应

当我们在进行大篇幅word文档的编写时&#xff0c;为了节约修改文章中图片或表格所花费的大量时间&#xff0c;可以将图片自动编号&#xff0c;且让文中引用的顺序跟着图片顺序的变化而变化&#xff0c;具体操作如下&#xff1a; 1. 将鼠标定位在图片或者表格欲加编号的下方或上…

ChatGPT有效开通方法*建议收藏*

ChatGPT Plus和API开通方法*建议收藏* 前期准备&#xff1a; 一个ChatGPT账户 一张虚拟卡 开通ChatGPT Plus会员&#xff0c;很多朋友担心虚拟卡订阅会封号&#xff0c;只要不勤换登陆IP不会使出现问题。而且目前大部分用户都是使用虚拟卡开通的会员 第一步&#xff1a;登…

基于STC12C5A60S2系列1T 8051单片读写掉电保存数据IIC总线器件24C02地址码并显示在液晶显示器LCD1602上应用

基于STC12C5A60S2系列1T 8051单片读写掉电保存数据IIC总线器件24C02地址码并显示在液晶显示器LCD1602上应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍液晶显示器…

大数据-计算框架选型与对比

计算框架选型与对比 一、大数据平台二、计算框架分类1.批处理架构2.实时流处理架构3.流批一体处理架构 三、计算框架关键指标1.处理模式2.可伸缩性3.消息传递3.1 至少一次&#xff08;at least once&#xff09;3.2 至多一次&#xff08;ai most once&#xff09;3.3 恰好一次&…

交叉编译安装时报错 ./install.sh: 15: ./install.sh: Bad substitution

报错信息截图如下&#xff1a; 解决方法 vim install.sh #!/bin/sh -e 修改为 !/bin/bash -e重新执行 sudo ./install.sh 成功运行

Leetcode算法系列| 1. 两数之和(四种解法)

目录 1.题目2.题解解法一&#xff1a;暴力枚举解法二&#xff1a;哈希表解法解法三&#xff1a;双指针(有序状态)解法四&#xff1a;二分查找(有序状态) 1.题目 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数…

Endnote软件添加期刊引用格式

在下述网址中&#xff0c;找到你想要添加的期刊&#xff0c;下载引用格式文件&#xff08;后缀为.ens格式&#xff09; https://endnote.com/downloads/styles/?wpv_post_searchInformationfusion&wpv_aux_current_post_id12829&wpv_view_count12764-TCPID12829 下载…

C语言——从键盘输人一个表示年份的整数,判断该年份是否为闰年,并显示判断结果。

#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h> int main() {int year 0;printf("请输入年份&#xff1a;");scanf("%d",&year);if((year%4 0) && (year%100!0) || (year%400 0)){printf("%d是闰年\n",year);}else{p…

人工标签不准确的一种解决方案:PCA降维可视化筛选正样本

背景 在实际的业务场景里&#xff0c;用会话文本构建模型&#xff08;机器学习/深度学习&#xff09;来做意图分类之类的任务时&#xff0c;经常会出现人工打标不够准确的问题&#xff0c;标签都不准确的话模型当然无法学习到有效信息了。这个问题真的非常头疼…除了与业务沟通…

【CCF-PTA】第03届Scratch第03题 -- 参观食品厂

参观食品厂 【题目描述】 光明小学定期组织学生参与社会实践&#xff0c;最近的活动主题是参观自动化食品厂。小明看到生产线可以自动筛选罐头重量是否合格&#xff0c;感到十分好奇。回来后&#xff0c;小明准备写一个模拟程序&#xff0c;输入10盒罐头的重量&#xff0c;程…

openGauss学习笔记-131 openGauss 数据库运维-启停openGauss

文章目录 openGauss学习笔记-131 openGauss 数据库运维-启停openGauss131.1 启动openGauss131.2 停止openGauss131.3 示例131.3.1 启动openGauss131.3.2 停止openGauss 131.4 错误排查 openGauss学习笔记-131 openGauss 数据库运维-启停openGauss 131.1 启动openGauss 以操作系…