四种cast转换

一、  const_cast

编译时执行,仅能用于同种数据类型间的转换。即常量属性转换。转换对象仅能为指针或引用。

#include<iostream>

using namespace std;


int main(){
	const int a = 123; //声明a为const常量
	
	
	a = 1234;   //报错,无法修改const属性的值
	cout << "a= " << a << endl;
	
	int* ptr = &a; //报错,int*无法指向const类型的值
	cout << "a= " << a << endl; 

        const int *ptr = &a;
  	cout << "a = " << a << endl;   // a = 123
  	cout << "ptr = " << ptr << endl; 
  	*ptr = 456;                    //出错,指针也无法修改const类型的值
  	cout << "a = " << a << endl;
  	
  	cout << endl;
  	
  	
  	int* ptr1 = const_cast<int*>(ptr);  //取消const属性
  	*ptr1 = *ptr1 + 1;                  //成功
  	cout << "a = " << a << endl;        //1235
  	cout << "*ptr = " << *ptr << endl;  //1235
  	cout << "*ptr1 = " << *ptr1 << endl; //1235
  	cout << "&a = " << &a << endl;
  	cout << "ptr = " << ptr << endl;
  	cout << "ptr1 = " << ptr1 << endl;
  	
	
	const int* ptr2 = const_cast<const int*>(ptr1);
  	*ptr2 += 1;
  	cout << "a = " << a << endl;
  	
  	
  	cout << endl;
  	const int& tmp = a;
  	cout << "a = " << a << endl;
  	
  	tmp += 1;
  	cout << "a = " << a << endl;
  	
  	
  	
  	int &tmp1 = const_cast<int&>(tmp);
	tmp1 += 1;
	cout << "a = " << a << endl;  	
	
}
#include<iostream> 

using namespace std;

int main(){
	/*
	int* p = new int(1);
	const int* p1 = const_cast<const int*>(p);
	*p = 123;
	*/
	const int a = 1;
	const int* p = &a;
	const int *p1 = const_cast<const int*>(p);
	*p1 = 123;
	cout << *p << " " << *p1 << endl;
	cout << p << " " << p1 << endl;
}

当我们使用const_cast为一个指针添加const属性时,仅仅是一个浅拷贝。所以之前非const属性的指针此时仍可以修改const所指向的地址的值。同样的,如果const指针指向一个const数据时,给这个指针去除const属性后,是可以修改const变量的。

二、reinterpret_cast

reinterpret_cast可以用于不同变量类型之间的转换,甚至可以将指针转为普通类型变量(实际就是将指针当中的地址转换为对于变量的格式)。它的本质是编译器指令,是四种cast转换中最为暴力的一种。在自由度如此高的情况下也存在很大的安全性问题。

#include<iostream> 

using namespace std;

int main(){
	int *p = new int(1);
	int a = reinterpret_cast<int>(p);
	float b = reinterpret_cast<int>(p);
	cout << "a = " << a << endl << "p = " << p << endl << "b = " << b <<endl;
}

三、static_cast

static_cast类似于C语言中的强制转换,类似于

double a = 123.123;
int b = (int)a;

主要用途是:

1)基本类型之间的相互转化

2)void*转换为其他类型指针

3)父类子类之间的相互转化

先来看个例子:

#include<iostream> 

using namespace std;

class A{
	public:
		int m_num = 666;
		int setnum(){
			return ++m_num;
		}
};

class B:public A{
	public:
		int m_num = 666;
		int setnum(){
			return --m_num;
		}
};


int main(){
	double a = 123.123;
	A* c = new A;
	int b = static_cast<int>(a);
	B* d = static_cast<B*>(c);
	d->setnum();
	cout << "b = " << b << endl << "d->num = " << d->m_num << endl;
}

运行结果如图所示,这里的double向int转换得到的结果符合预期,没什么问题。但是A与B类对象指针的转换结果却不符合预期,这是为什么呢?

这里A与B之间是父子继承关系,在B中继承了A的所有属性,但是A中却不存在纯属于B的属性。所以如果父类指针向子类指针转换,子类所指向的范围是大于父类的,当调用子类当中的某些成员是也是未初始化的部分。但是调用方法时却不会发生任何问题,这是因为类方法是存储在.text段的,是程序在编译好就确定的。而成员变量是存储在每个对象当中的,创建每一个对象时都需要进行初始化。

如果改成,子类指针转换为父类指针时,就不会发送这一问题。可以理解为大范围变为小范围内存时,不会发生访问越界。如:

#include<iostream> 

using namespace std;

class A{
	public:
		int m_num = 666;
		int setnum(){
			return ++m_num;
		}
};

class B:public A{
	public:
		int m_num = 666;
		int setnum(){
			return --m_num;
		}
};


int main(){
	double a = 123.123;
	B* c = new B;
	int b = static_cast<int>(a);
	//B* d = static_cast<B*>(c);
	A* d = static_cast<A*>(c);
	d->setnum();
	cout << "b = " << b << endl << "d->num = " << d->m_num << endl;
}

如果不是父子关系的static的转换是不被允许的,编译器会直接抛出错误。

如果不是指针间的转换,而是对象间类型转换。此时会调用对应类的拷贝构造函数,如果没有定义相关的拷贝构造函数,那么编译器同样会抛出错误。

四、dynamic_cast

关于dynamic_cast,它主要是用于同层类之间的类型转换,或是指针或引用不同层级间的下行转换。

比如一个基类对象的指针,可以通过dynamic_cast安全的转换为派生类对象指针。为什么说是安全的转换呢?这里就和static_cast进行比对了。前面提到的static_cast很容易出现派生类指针指向一个父类对象,这样对于指针来讲就是指向了一个不完整的内存空间,很容易出现非法访问的情况。而dynamic_cast会在转换时识别所指向的对象类型。

如果能正确转换则返回对应类型指针,不能转换则返回空指针。

如果引用不能正常转换则会抛出异常。

所以当我们在使用dynamic进行类型转换时需要对返回的指针进行校验。

同时,dynamic_cast的使用必须要求对应的类包含虚函数。因为dynamic_cast是C++当中RTTI的一种,RTTI的具体实现依赖于虚函数表中的type_info对象。

type_info是一个构造函数、拷贝构造和赋值都非公有的一个类。其中的name方法可以输出当前类的类型名称。而在多继承的情况下,派生类存在多个虚函数指针,他们分别指向虚函数表中父类继承过来的部分。而在指针的前一位置(前4字节),则存放的是type_info对象的指针,并且指向的是同一地址。所以无论当前指针是什么类型,我们都可以以此准确的推断出当前指向对象的类型。

关于RTTI这一特性,后面会专门写一篇文章来介绍。

点赞

发表回复

电子邮件地址不会被公开。必填项已用 * 标注