C++学习之路-类型转换

C语言中的类型转换

通常,C语言的类型转换风格是下面两种

  • (type)expression
  • type (expression)

类似于我们理解的强制转换

int a = 10;
double b = (double) a;
double c = double(a);

当前,就算不写强制转换,只有变量声明的类型和赋值的类型不同。C语言也会默认进行隐式转换

int a = 10;
double b = a; //b = 10.00000

C++中的类型转换

C++中有4个类型转换符

  • static_cast
  • dynamic_cast
  • reinterpret_cast
  • const_cast

具体使用时的格式为:

xxxx_cast<type>(expression)

const_cast:一般用于去除const属性(将const转换为非const)

比如说定义了一个const类型的指针a,想要将a赋值给非const指针b,就会出错。
在这里插入图片描述

于是,可以这样解决:这样就完成了const类型到非const类型的转换

在这里插入图片描述

const_cast中的类型必须是指针,引用,或者指向对象类型成员的指针

在这个过程中,我遇到了一个细节问题。当我转换基本数据类型int时,不可以转换。

在这里插入图片描述
所以,const_cast只能用在指针或引用类型之间的转换上。

比如说我想要重新定义一个对象指针p2,指向已经存在的const对象指针p1,这是不被允许的。

在这里插入图片描述
但是,我就是有这样的需求,需要另外一个指针指向这块区域。那我就可以采用const_cast强制转换为非const的:

const Person *p1 = new Person();
Person *p2 = const_cast<Person*>(p1);

但是!!!!并不是说const转换为非const就必须使用const_cast,而是const_cast可以做这件事而已!!!

这样也可以办到const到非const的转换。只不过const_cast更加清晰明了,让开发人员直接就能看出来这是const转为非const的过程。程序的可读性强而已。

const Person *p1 = new Person();
Person *p3 = (Person *) p1;

这两种转换本质上没有任何区别,只是可读性更强而已。

在这里插入图片描述

所以,const_cast,我们只需要能看懂别人写的代码就行,可有可无的存在,没有什么优秀特性,唯一的优点就是可读性强。

dynamic_cast:一般用于多态类型的转换(有安全检测功能)

一般用于多态类型的转换,额外有类型之间的安全检测过程。这个我们需要额外重视

什么叫多态,可以看之前的博文。第一部分就是说明父类指针可以指向子类对象,而子类指针不可以指向父类对象

  • 首先定义两个类,实现多态。相信可以看明白(因为只有虚函数才有多态)
class Person
{
	virtual void run(){}
public:
	int m_age;

};

class Student : public Person {
public:
	int m_score;
};
  • 声明两个父类Person指针对象,分别指向不同的对象
Person *p1 = new Person();  //父类指针指向父类对象
Person *p2 = new Student(); //父类指针指向子类对象

子类指针为什么不可以指向父类对象

这里多说一句:为什么子类指针不允许指向父类对象?

因为某种类型的指针,只可以访问其本身的成员。比如:Person类型的指针,只可以访问 m_age,而不可以访问m_score;Student类型的指针就可以访问m_age和m_score。

在这里插入图片描述
在这里插入图片描述

指向什么类型的对象,也就意味着指针可以访问这个对象的内存区域。比如:指向Person对象,那就可以访问Person对象里的内存;指向Student对象,那就可以访问Student对象里的内存。

现在说说为什么父类指针可以指向子类对象,子类指针不可以指向父类对象!!

因为这里面涉及到安全问题。也就是说父类指针指向子类对象没有安全问题,而子类指针指向父类对象就存在安全问题

下面举一个例子,就明白了了!我们定义了两个指针,分别指向不同的对象,可以看明白吧

Person *p1 = new Student ();  //父类指针指向子类对象 (安全)
Student *s1 = new Person ();  //子类指针指向父类对象 (不安全)

p1可以访问m_age,同时p1指向的区域是Student(有m_age和m_score),结果就是p1访问不到子类Student里的m_score。但这又何妨呢?访问不到就访问不到呗,起码不会乱访问。

p2可以访问m_age和m_score,但是p2指向的区域是Person (只有m_age),也就意味着p2可以访问到m_age,但是p2->m_score在程序中并不会报错,而是指向Person对象内存中m_age的下四个字节。但是,这四个字节不是Person对象的内存,很可能是别的对象的内存,这就乱访问了,p2->m_score的值就不是我们想要访问的值了,就会出现安全问题。

所以C++不允许这种情况出现,直接报错!!!
在这里插入图片描述

但是,如果我非要这么做,可以用强制转换,达到编译器不报错的目的。但是安全问题依然存在!!!

在这里插入图片描述

为了避免开发人员强制转换所带来的安全问题。我们可以使用dynamic_cast进行强制转换,并进行安全检测!如果发现是子类指针指向父类对象,那返回的指针为nullptr,就不会产生安全问题(不会乱访问未知的内存区域)

Person *p1 = new Person();  //指向父类
Person *p2 = new Student();	//指向子类

cout << "p1 = " << p1 << endl; //p1 = 000001EE091900D0
cout << "p2 = " << p2 << endl; //p2 = 000001EE091894B0

Student *stu1 = (Student *)p1;                  //stu1 = 000001EE091900D0
Student *stu2 = dynamic_cast<Student *>(p1);	//stu2 = 0000000000000000
Student *stu3 = dynamic_cast<Student *>(p2);	//stu3 = 000001EE091894B0

可以看到stu1和p1的值是一样的,说明这种强转直接将p1的内容给了stu1,也就是说stu1依然指向了Person对象,而且编译器还没报错;

在使用dynamic_cast强转之后,发现stu2 虽然接受的是p1的值,但是stu2通过安全检测,发现存在子类指针指向父类对象的操作,于是dynamic_cast将stu2清零,这样就是一个nullptr,就不会出现乱访问的状况了。

由于stu3指向的也是本身类的对象,是子类指针指向子类对象,所以不存在子类指针指向父类对象的操作。因此不会被安全监测,也就不会被清零了。

交叉转换也会被dynamic_cast检测出来

不只是,子类指针指向父类对象不被允许。只要是不合乎逻辑的操作(比如交叉转换),都是可以被dynamic_cast检测出来

假设我定义了一个Car类,跟Person和Student 一点关系都没有

class Person
{
	virtual void run(){}
public:
	int m_age;

};

class Student : public Person {
public:
	int m_score;
};

class Car
{
};

一点关系没有,我们也可以通过强转让其赋值。如果是直接强转,就是把值赋过来拉倒。如果是采用dynamic_cast强转,则会进行安全监测(由于Car指针不可以随意访问与其一点关系没有的Person对象),所以就会将赋值过来的p1置为nullptr

Person *p1 = new Person(); 
cout << "p1 = " << p1 << endl;//p1 = 000001FB10922770

Car *c1 = (Car *)p1;
Car *c2 = dynamic_cast<Car *>(p1);

cout << "c1 = " << c1 << endl; //c1 = 000001FB10922770
cout << "c2 = " << c2 << endl; //c2 = 0000000000000000

总结:我们几乎不会犯子类指针指向父类对象(不合乎逻辑)的操作,但是要能看懂别人写这样的代码的含义。

static_cast:常用于基本数据类型转换,比如非const转换为const

  • 对比dynamic_cast,缺乏安全监测

上面的代码要这样写,stu2就不会被置为nullptr了

Student *stu2 = static_cast<Student *>(p1);	//stu2 = 000001EE091900D0
  • 不能交叉转换(不是统一继承体系的,无法转换)

什么叫交叉转换?Person和Car没有任何联系,强行转换叫做交叉转换。

Person *p1 = new Person(); 

Car *c1 = (Car *)p1;
Car *c2 = dynamic_cast<Car *>(p1);

而static_cast是不可以完成这样的操作的,编译器会直接报错:类型转换无效
在这里插入图片描述

对比完与dynamic_cast的区别,我们说一下static_cast的特点:

  • 两个代码没有任何区别,static_cast反而更麻烦
int a = 10;
double b = static_cast<double>(a);
int a = 10;
double b = a;
  • 非const转为const
int *a = new int();
const int *b = static_cast<const int*>(a);

Person *p1 = new Person();
const Person *p2 = static_cast<const Person *>(p1);

基本没啥用,不写static_cast<const Person *>,跟隐式转换的效果是一模一样的。也就是写不写程序执行的效果是一样的。

reinterpret_cast:没有任何类型检查和格式转换,仅仅是简单的二进制数据的拷贝

这个有些不太一样,需要注意一下。

reinterpret_cast属于比较底层的强制转换,没有任何类型检查和格式转换,仅仅是简单的二进制数据的拷贝。

比如:我们定义一个int型的变量,赋值10。

int a = 10;

我们通过监视,输入&a,就可以查看到存储a的内存,并给出了所在的栈空间地址

在这里插入图片描述

通过地址值,我们就可以在内存中查找到,该块内存区域的内容。可以看到,是 0a 00 00 00 也就是整数10的16进制表示。还可以看到,其他内存区域都是cc,这就是栈空间的特点,全部初始化为cc。

在这里插入图片描述

接下来,我们进行了一个隐式转换的操作。将int转换为double

int a = 10;
double d = a;

获取到double型变量d的地址

在这里插入图片描述

查看被赋值的double内容,确实是double类型的数字10.000000…

在这里插入图片描述

但是内存中的16进制,我有些看不懂。什么 24 40 我怎么算也算不出是10。

在这里插入图片描述

这是因为double、float这种浮点数类型的数据存储方式与int不同(存储尾数和指数这种)。会额外做一些处理,显示的16进制也就不是我们理解的0a这种。具体看这篇博客:浮点数double在内存中的存储方式

我们了解了int和double内存的存储方式不同,导致二进制数据是不统一含义的。我们解释了这么多,就是为了感受一下reinterpret_cast所谓的仅仅是二进制数据的拷贝是什么意思。

我们将上述的隐式类型转换改为reinterpret_cast转换,<>为引用类型:不同类型转换需要引用,int* 和int转换就不需要引用。

int a = 10;
double d = reinterpret_cast<double&>(a);

可以看到,d并不是double类型的10.000000…,而是认不出来的数

在这里插入图片描述

得到d的地址

在这里插入图片描述

查看内存中的数据,我们发现:double类型占8个字节,前4个字节被 int 10 的二进制数据覆盖,而后4个字节依然是栈空间初始化的cc。这就导致 double d 的内存内容是 0a 00 00 00 cc cc cc cc。

在这里插入图片描述

这就是简单的二进制赋值,不会考虑类型是啥,reinterpret_cast就是直接赋值。

下面的图,更能清晰的说明这个过程:

  • 隐式转换,会考虑类型,达到我们想要的转换。由于double存储方式,所以我们看到的二进制代码很奇怪,但是不影响结果

在这里插入图片描述

  • reinterpret_cast强制转换,只进行简单的二进制代码的复制,不检查左右两边类型。

在这里插入图片描述

到这里,并不是告诉开发人员以后采用reinterpret_cast强制转换。我的目的是告诉开发人员,这个reinterpret_cast的功能是什么,在日常开发过程中这么写就出错了。但是,reinterpret_cast依然有他自己的应用场景。

总结:reinterpret_cast也不会有安全监测,就是把右边的内存中的二进制数据赋值给左边的存储空间,只要二进制存储的方式相同,就不会出现解析不了的问题。

补充一点,reinterpret_cast可以将指针和整数互相转换

int *p = reinterpret_cast<int*>(0x0100);  // p是指针,存储的0x0100
cout << "p = " << p << endl;
int num = reinterpret_cast<int>(p); //将地址转换为整数,256
cout << "num = " << num << endl; //256

总结

这些转换类型符可能根本就用不到,但是要知道功能是啥,也就是说要能看懂别人写的代码。仅此而已。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值