栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

【C++】强制类型转换

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【C++】强制类型转换

C++强制类型转换 1. static_cast

static_cast是最常用的C++风格的强制类型转换,主要是为了执行那些较为合理的强制类型转换。使用格式如下:
static_cast(expression);

(1)用于基本内置数据类型之间的转换

内置类型之间的转换往往可以由隐式转换自动执行,而不需要人工特地去执行强制类型转换。由于转换结果可能存在截断性,这种转换的安全性要由开发人员来保证,编译器可能会适当打印警告信息。

void test01()
{
	
	char type_char = 'A';
	// 隐式类型转换
	float type_float = type_char;
	cout << type_float << endl;		// 65
	// 显式地强制类型转换
	float type_float_cast = static_cast(type_char);
	cout << type_float << endl;		// 65

	
	double type_double = 1.23;
	// 隐式类型转换
	long type_long = type_double;
	cout << type_long << endl;		// 1
	// 显式地强制类型转换
	long type_long_cast = static_cast(type_double);
	cout << type_long << endl;		// 1
}
(2)用于指针之间的转换

不同类型的指针不能强制转换。用途:在隐式转换下,void*类型无法转换为其它类型指针,这时候需要借助static_cast来转换。static_cast不允许不同类型的引用进行转换,因为没有void类型引用可以作为中间介质。

void test02()
{
	
	int type_int = 10;
	// 隐式转换无效
	//float* float_ptr1 = &type_int;
	// 显式地强制类型转换无效
	//float* float_ptr2 = static_cast(&type_int);

	
	// 任何类型的指针都可以隐式转换为void*
	void* void_ptr = &type_int;
	// 隐式转换无效
	//float* float_ptr3 = void_ptr;
	// 显式地强制类型转换
	float* float_ptr4 = static_cast(void_ptr);
}
(3)不能转换掉表达式的const或volitale属性
void test03()
{
	int temp = 10;

	
	const int* a_const_ptr = &temp;
	//int* b_const_ptr = static_cast(a_const_ptr);

	
	const int a_const_ref = 10;
	//int& b_const_ref = static_cast(a_const_ref);

	
	volatile int* a_vol_ptr = &temp;
	//int* b_vol_ptr = static_cast(a_vol_ptr);

	
	volatile int a_vol_ref = 10;
	//int& b_vol_ref = static_cast(a_vol_ref);
}
(4)用于类实例的之间转换

进行上行转换是安全合法的,这个过程由隐式转换来完成也合法。进行下行转换时,static_cast会认为两个类无关联,这种转换不合法。

class A {
public:
    int a;
};

class B {
public:
    int b;
};

class C : public A, public B {
public:
    int c;
};

void test04()
{
	C c;
	
	// 隐式转换
	A a1 = c;
	B b1 = c;
	// 显式地强制类型转换
	A a2 = static_cast(c);
	B b2 = static_cast(c);

	
	A a3;
	B b3;
	// 隐式转换无效
	//C c1 = a3;
	//C c2 = b3;
	// 显式地强制类型转换无效
	//C c_a = static_cast(a);	
	//C c_b = static_cast(b);
}
(5)用于没有多态的类实例指针或引用之间的转换
  • 进行上行转换(派生类指针 -> 基类指针、派生类引用 -> 基类引用)是完全安全的,这个过程由隐式转换来完成也合法。
  • 进行下行转换(基类指针 -> 派生类指针、基类引用 -> 派生类引用)缺乏安全检查,要尽量避免这种用法。
  • 如果两个类无继承关系,转换时会失败,但是这种情况下会显性地展示出错误信息,是安全的。
class A {
public:
    int a;
};

class B {
public:
    int b;
};

class C : public A, public B {
public:
    int c;
};

void test05()
{
	
	C c;
	// 指针
	A* a_ptr = static_cast(&c);
	B* b_ptr = static_cast(&c);
	// 引用
	A& a_ref = static_cast(c);
	B& b_ref = static_cast(c);

	
	// 指针
	C* c_ptra = static_cast(a_ptr);
	C* c_ptrb = static_cast(b_ptr);
	// 引用
	C& c_refa = static_cast(a_ref);
	C& c_refb = static_cast(b_ref);
}
(6)用于具有多态的类实例指针或引用之间的转换
  • 进行上行转换(派生类指针 -> 基类指针、派生类引用 -> 基类引用)是安全的,这个过程由隐式转换来完成也合法。
  • 进行下行转换(基类指针 -> 派生类指针、基类引用 -> 派生类引用)由于缺乏安全检查,并且因为具有多态的类往往具有特殊的用法,产生的后果严重,要尽量避免这种用法。
class A {
public:
	virtual void print() {
		cout << "A" << endl;
	}
};

class B {
public:
	virtual void print() {
		cout << "B" << endl;
	}
};

class C : public A, public B {
public:
	virtual void print() override {
		cout << "C" << endl;
	}
};

void test06()
{
	C c;
	
	// 指针
	A* a_ptr = static_cast(&c);
	B* b_ptr = static_cast(&c);
	a_ptr->print();		// C
	b_ptr->print();		// C
	// 引用
	A& a_ref = static_cast(c);
	B& b_ref = static_cast(c);
	a_ref.print();		// C
	b_ref.print();		// C      

	C& c_refa = static_cast(a_ref);
	C& c_refb = static_cast(b_ref);
	c_refa.print();		// C
	c_refb.print();		// C
}

void test07()
{
	A a;
	B b;
	
	// 指针
	C* c_ptra = static_cast(&a);
	C* c_ptrb = static_cast(&b);
	c_ptra->print();	// A
	c_ptrb->print();	// 段错误

	C& c_refa = static_cast(a);
	C& c_refb = static_cast(b);
	c_refa.print();		// A
	c_refb.print();		// 段错误
}

原因:类A第一个被继承,类B第二个被继承,在类C中,第一个虚表指针指向类A的虚表,第二个虚表指针指向类B的虚表。在上面的例子那样进行错误地转换时,由于类A被继承类C继承,它位置的特殊性导致可以使用c_ptra正确地调用类A的print()方法,而类B则不行。对于类C来说,它始终调用着_vptr.A指向的print()方法,当我们使用纯类B类型进行下行转换时,根本就没有这一块的数据(这个转换是不完整的、不安全的),所以就会出现段错误了。当然,使用纯类A类型进行下行转换也是不完整、不安全的,只不过位置刚好才不会出现段错误而已。

小结

static_cast相比C风格的强制类型转换要安全很多,有很大程度上的类型安全检查。同时我们也要认识到其缺点:

  1. 无法消除const和volatile属性
  2. 无法直接对两个不同类型的指针或引用进行转换
  3. 下行转换无类型安全检查

不过没关系,其它三个强制类型转换的关键字刚好能弥补static_cast的这些缺点。

2. const_cast

const_cast的作用是去除掉const或volitale属性,使用格式如下:const_cast(expression)
注意事项:const_cast不是用于去除变量的常量性,而是去除指向常量对象的指针或引用的常量性,其去除常量性的对象必须为指针或引用,并且const_cast不支持不同类型指针或引用之间的转换,比如说float转换成int是不允许的,直白一点说就是type_id和expression要基本类型保持一致,相差的话只能差const或volatile属性。

(1)const_cast作用对象为指针或引用,不支持不同类型指针或引用之间的转换
void test01()
{
	int type_int = 100;
	int& type_int_ref = type_int;
	// 作用对象为指针或引用
	//float type_float = const_cast(type_int);
	// 不支持不同类型指针或引用之间的转换
	//float* type_float_ptr = const_cast(&type_int);
}
(3)去除const属性,并没有重新申请内存把需要转换的变量给复制过去

const_cast不用于去除变量的常量性,type_const_int对应地址的内容确实被改变了,但是type_const_int的值却并没有被改变,这是好事,因为从一开始我们把它定义为常量类型时这个值就不应该再被改变了。后面使用type_const_int_ptr和type_const_int_ref试图去改变type_const_int的值,这是很危险的做法,不同编译器可能会有不同的处理,是有可能出现严重错误的,要杜绝这种用法。

void test02()
{
	const int type_const_int = 100;
	int* type_int_ptr = const_cast(&type_const_int);
	int& type_int_ref = const_cast(type_const_int);

	*type_int_ptr = 10;
	cout << *type_int_ptr << endl;	// 10
	cout << type_const_int << endl;	// 100

	type_int_ref = 20;
	cout << type_int_ref << endl;	// 20
	cout << type_const_int << endl;	// 100

	// 说明const_cast只是去除了const属性,并没有重新申请内存把需要转换的变量给复制过去
	cout << "&type_const_int: " << &type_const_int << endl;	// 0059F7BC
	cout << "type_int_ptr: " << type_int_ptr << endl;		// 0059F7BC
	cout << "&type_int_ref: " << &type_int_ref << endl;		// 0059F7BC
}
(3)适用情景

如果一个变量本来不具备const属性,但是在传递过程中被附加了const属性,使用const_cast清除掉附加的const属性。

void fun(const int& val)
{
	int& type_int_ref = const_cast(val);
	type_int_ref = 10;
}

void test03()
{
	int type_int = 100;
	fun(type_int);
	cout << type_int << endl;	// 10
}
3. reinterpret_cast

reinterpret_cast意为“重新解释”,它是C++中最接近于C风格强制类型转换的一个关键字。它让程序员能够将一种对象类型转换为另一种,不管它们是否相关。使用格式如下:reinterpret_cast(expression)

  1. type-id和expression中必须有一个是指针或引用类型(可以两个都是指针或引用,指针引用在一定场景下可以混用,但是建议不要这样做,编译器也会给出相应的警告)。
  2. reinterpret_cast的第一种用途是改变指针或引用的类型。
  3. reinterpret_cast的第二种用途是将指针或引用转换为一个整型,这个整型必须与当前系统指针占的字节数一致。
  4. reinterpret_cast的第三种用途是将一个整型转换为指针或引用类型。
  5. 可以先使用reinterpret_cast把一个指针转换成一个整数,再把该整数转换成原类型的指针,还可以得到原先的指针值(由于这个过程中type-id和expression始终有一个参数是整形,所以另一个必须是指针或引用,并且整型所占字节数必须与当前系统环境下指针占的字节数一致)
  6. 使用reinterpret_cast强制转换过程仅仅只是比特位的拷贝,和C风格极其相似(但是reinterpret_cast不是全能转换,详见第1点),实际上reinterpret_cast的出现就是为了让编译器强制接受static_cast不允许的类型转换,因此使用的时候要谨而慎之
  7. reinterpret_cast不能转换掉expression的const或volitale属性。
#include 
using namespace std;

class A {
public:
	A() : _a(0) {}
public:
	int _a;
};

class B {
public:
	B() : _b(0) {}
public:
	int _b;
};

void test01()
{
	float type_float = 10.1;

	//int type_int = reinterpret_cast(type_float);					// 错误1,type-id和expression中必须有一个是指针或者引用类型
	//char type_char = reinterpret_cast(&type_float);				// 错误3,64位系统,这里type_id只能是long类型
	//double* type_double_ptr = reinterpret_cast(type_float);	// 错误4,expression只能是整型

	A a;
	B b;
	//long type_long = reinterpret_cast(a);	// 错误1,type-id和expression中必须有一个是指针或者引用
	//B b1 = reinterpret_cast(a);				// 错误1,type-id和expression中必须有一个是指针或者引用
	//A a1 = reinterpret_cast(&b);				// 错误3,64位系统,type-id只能是long类型
	//A* a_ptr = reinterpret_cast(b);			// 错误4,expression只能是整型
}

void test02()
{
	float type_float = 10.1;
	long type_long = reinterpret_cast(&type_float);			// 正确3,float* -> long
	float* type_float_ptr = reinterpret_cast(type_long);	// 正确4,long -> float*
	cout << *type_float_ptr << endl;	// 10.1						// 正确5

	long* type_long_ptr = reinterpret_cast(&type_float);		// 正确1,float* -> long*

	char type_char = 'A';
	double& type_double_ptr = reinterpret_cast(type_char); // 正确4,char -> double&

	A a;
	B b;
	long a_long = reinterpret_cast(&a);		// 正确3,A* -> long
	A* a_ptr1 = reinterpret_cast(type_long);	// 正确4,long -> A*
	A* a_ptr2 = reinterpret_cast(&b);			// 正确1,B* -> A*
}

int main()
{
	//(1)错误用法
	test01();
	//(2)正确用法
	test02();

	system("pause");
	return 0;
}
(4)dynamic_cast

dynamic_cast是运行时处理的,使用格式如下:dynamic_cast(expression);
注意事项如下:

(1)dynamic_cast是运行时处理的,运行时会进行类型检查。
(2)dynamic_cast不能用于内置基本数据类型的强制转换,并且dynamic_cast只能对指针或引用进行强制转换。
(3)dynamic_cast如果转换成功的话返回的是指向类的指针或引用,转换失败的话则会返回nullptr。
(4)使用dynamic_cast进行上行转换时,与static_cast的效果是完全一样的。
(5)使用dynamic_cast进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。并且这种情况下dynamic_cast会要求进行转换的类必须具有多态性,否则编译不通过。需要有虚表的原因:类中存在虚表,就说明它有想要让基类指针或引用指向派生类对象的情况,dynamic_cast认为此时转换才有意义(事实也确实如此)。而且dynamic_cast运行时的类型检查需要有运行时类型信息,这个信息是存储在类的虚表中的
(6)在C++中,编译期的类型转换有可能会在运行时出现错误,特别是涉及到类对象的指针或引用操作时,更容易产生错误。dynamic_cast则可以在运行期对可能产生问题的类型转换进行测试。

#include 
using namespace std;

class A
{
public:
	void print()
	{
		cout << "A" << endl;
	}
};

class B
{
public:
	void print()
	{
		cout << "B" << endl;
	}
};

class C : public A, public B
{
public:
	void print()
	{
		cout << "C" << endl;
	}
};

void test01()
{
	C c;
	A* a_ptr = dynamic_cast(&c);	// 正确4,上行转换没有任何要求
	B* b_ptr = dynamic_cast(&c);	// 正确4,上行转换没有任何要求

	//A a = dynamic_cast(c);	// 错误2,无法用于转换类实例

	//C* c_ptra = dynamic_cast(a_ptr); // 错误5,类C不具备多态,无法进行下行转换
	//C* c_ptrb = dynamic_cast(b_ptr); // 错误5,类C不具备多态,无法进行下行转换
}

int main()
{
	test01();
	system("pause");
	return 0;
}
#include 
using namespace std;
class A
{
public:
	virtual void print()
	{
		cout << "A" << endl;
	}
};

class B
{
public:
	virtual void print()
	{
		cout << "B" << endl;
	}
};

class C : public A, public B
{
public:
	virtual void print()
	{
		cout << "C" << endl;
	}
};

void test01()
{
	C c;

	// 第一组
	A* a_ptr = dynamic_cast(&c);
	B* b_ptr = dynamic_cast(&c);
	C* c_ptra = dynamic_cast(a_ptr); // 正确,类C具备多态性,可以使用dynamic_cast进行下行转换
	C* c_ptrb = dynamic_cast(b_ptr); // 正确,类C具备多态性,可以使用dynamic_cast进行下行转换

	cout << &c << endl;		// 0084F9FC
	cout << c_ptra << endl;	// 0084F9FC
	cout << c_ptra << endl;	// 0084F9FC

	// 第二组
	A a;
	B b;
	C* c_ptra1 = dynamic_cast(&a); // 编译正常,转换结果为nullptr,说明转换不合法
	C* c_ptrb1 = dynamic_cast(&b); // 编译正常,转换结果为nullptr,说明转换不合法

	cout << c_ptra1 << endl;	// 00000000
	cout << c_ptrb1 << endl;	// 00000000
}

int main()
{
	test01();
	system("pause");
	return 0;
}

参考:https://blog.csdn.net/weixin_43798887/article/details/118424172

我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号