面试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;// pxcout << 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.mzph.cn/news/164249.shtml

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

相关文章

Selenium实现多页面切换

当使用 Selenium 进行自动化测试或爬取数据时&#xff0c;有时需要处理多个页面之间的切换。以下是一些可能需要多页面切换的情况&#xff1a; 1、打开新窗口/页面&#xff1a; 在当前页面上点击链接、按钮或执行某些操作时&#xff0c;可能会打开一个新的窗口或页面。此时&a…

【element优化经验】怎么让element-ui中表单多语言切换排版不乱

目录 前言&#xff1a; 痛点&#xff1a; 1.左对齐&#xff0c;右对齐在中文和外语情况下字数不同&#xff0c;固定宽度会使名称换行&#xff0c;不在整行对齐&#xff0c;影响美观。 2.如果名称和输入框不在一行&#xff0c;会使页面越来越长 3.label-width值给变量&#…

随笔记录-springmvc_ResourceHandlerRegistry+ResourceHttpRequestHandler

环境&#xff1a;springboot-2.7.5 配置文件配置静态资源映射 springboot配置静态资源映射方式是通过 WebMvcAutoConfiguration 实现的 spring: # resources: # # 自springboot 2.5.5之后&#xff0c;该属性已经被废弃&#xff0c;使用spring.web.resources.static-locat…

爬虫逆向你应该懂得Javascript知识

背景 大家在学习爬虫逆向的时候&#xff0c;一般都会涉及到对js源文件进行代码扣去&#xff0c;但是有的时候&#xff0c;你最好有js基础&#xff0c;能发现加密或者解密在那个位置&#xff0c;或者是能用python改写js代码&#xff0c;这就对个人的Javascript的能力有一定要求…

Switch的使用及其注意事项

注意第五点要看清&#xff0c;case执行完后匹配没有成功&#xff0c;如过有Default&#xff0c;将会执行Default&#xff0c;如果有case在Default之后&#xff0c;而且Default没有break语句&#xff0c;那么将会继续执行case的语句&#xff0c;此时case中的常量表达式只起语句标…

【Skynet 入门实战练习】游戏模块划分 | 基础功能模块 | timer 定时器模块 | logger 日志服务模块

文章目录 游戏模块基础功能模块定时器模块日志模块通用模块 游戏模块 游戏从逻辑方面可以分为下面几个模块&#xff1a; 注册和登录网络协议数据库玩法逻辑其他通用模块 除了逻辑划分&#xff0c;还有几个重要的工具类模块&#xff1a; Excel 配置导表工具GM 指令测试机器人…

系列一、Spring整合MyBatis不忽略mapper接口同目录的xxxMapper.xml

一、概述 默认情况下maven要求我们将xml配置、properties配置等都放在resources目录下&#xff0c;如果我们强行将其放在java目录&#xff0c;即将xxxMapper.xml和xxxMapper接口放在同一个目录下&#xff0c;那么默认情况下maven打包时会将这个xxxMapper.xml文件忽略掉&#xf…

【办公常识_1】写好的代码如何上传?使用svn commit

首先找到对应的目录 找到文件之后点击SVN Commit

【标注数据】labelme的安装与使用

这里写目录标题 下载标数据 下载 标数据 打开自动保存 创建矩形

NSGA-II求解微电网多目标优化调度(MATLAB)

一、NSGA-II简介 NSGA-Ⅱ算法是Kalyanmoy Deb等人于 2002年在 NSGA 的基础上提出的&#xff0c;它比 NSGA算法更加优越&#xff1a;它采用了快速非支配排序算法&#xff0c;计算复杂度比 NSGA 大大的降低&#xff1b;采用了拥挤度和拥挤度比较算子&#xff0c;代替了需要指定的…

Design Guidelines for 100 Gbps

文章目录 Stratix V GT Transceiver ChannelsCFP2 Host Connector Assembly and PinoutStratix V GT to CFP2 Interface Layout DesignBoard Stack Up DimensionsExample Design Channel PerformanceSimulation Results for Stratix V GT to CFP2 Connector Layout Design Desi…

特征工程完整指南 - 第二部分

苏米特班迪帕迪亚 照片由Dan Cristian Pădureş在Unsplash上拍摄 一、说明 DATA&#xff0c;通常被称为原油&#xff0c;需要经过加工和清洁才能有效地用于各种用途。正如我们不直接使用来自其来源的石油一样&#xff0c;数据也经过类似的处理以提取其真正价值。 二、特征选…

LabVIEW中如何达到NI SMU最大采样率

LabVIEW中如何达到NI SMU最大采样率 NISMU的数字化仪功能对于捕获SMU详细的瞬态响应特性或表征待测设备&#xff08;DUT&#xff09;响应&#xff08;例如线性调整率和负载调整率&#xff09;至关重要。没有此功能&#xff0c;将需要一个外部示波器。 例如&#xff0c;假设在…

Docker start/stop/restart 命令

docker start&#xff1a;启动一个或多个已经被停止的容器。 docker stop&#xff1a;停止一个运行中的容器。 docker restart&#xff1a;重启容器。 语法 docker start [OPTIONS] CONTAINER [CONTAINER...]docker stop [OPTIONS] CONTAINER [CONTAINER...]docker restart…

设计循环队列(详解)

呀哈喽&#xff0c;我是结衣 今天给大家带来的内容如标题所述&#xff0c;我们来设计环形队列&#xff0c;虽然队列没有讲&#xff0c;但是我就是想讲啊。那么环形队列现在开始。 队列的属性 在设计环形队列前&#xff0c;我们先要了解队列的特点&#xff08;先进先出&#x…

鸿蒙(HarmonyOS)应用开发——ArkTs学习准备

介绍 前面我们已经介绍了&#xff0c;如何安装HarmonyOS的IDE ,那么现在我们来介绍一下。HarmonyOS 开发的语言——ArkTs. ArkTS 是HarmonyOS的开发语言&#xff0c;他是typescript 的扩展&#xff0c;而typesrcipt是javascript的超集&#xff0c;如果你不太熟悉typescript语法…

qml Loader使用介绍

QML Loader 是 Qt Quick 框架中的一个元素,它允许你动态地加载和卸载 QML 组件。Loader 的作用主要体现在以下几个方面: 延迟加载:Loader 允许你在需要时才加载组件,而不是在应用程序启动时一次性加载所有组件。这样可以加快应用程序的启动时间,因为它只需要初始化用户当前…

MIT_线性代数笔记:列空间和零空间

目录 前言子空间综述列空间 Column space零空间&#xff08;或化零空间&#xff09;Nullspaceb 值的影响 Other values of b 前言 本节继续研究子空间&#xff0c;特别是矩阵的列空间&#xff08;column space&#xff09;和零空间&#xff08;nullspace&#xff09;。 子空间…

FreeRTOS的并行与并发思考

FreeRTOS的任务触发是由滴答时钟触发SysTick中断来触发调度器执行或阻塞或挂起和切换任务的。 首先是任务的并发能力&#xff0c;FreeRTOS的任务执行是基于全抢占调度机制&#xff0c;任务优先级按在就绪列表中由高到低排布&#xff0c;系统首先执行最高优先级任务&#xff0c;…