【C++】类型转换

类型转换

  • 一、C语言中的类型转换
  • 二、C++强制类型转换
    • 1. static_cast
    • 2. reinterpret_cast
    • 3. const_cast
    • 4. dynamic_cast

一、C语言中的类型转换

在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换

  1. 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败
  2. 显式类型转化:需要用户自己处理

例如:

				void Test()
				{
					int i = 1;
					// 隐式类型转换
					double d = i;
					printf("%d, %.2f\n", i, d);
				
					int* p = &i;
					// 显示的强制类型转换
					int address = (int)p;
					printf("%x, %d\n", p, address);
				}

缺陷:转换的可视性比较差,所有的转换形式都是以一种相同形式书写,难以跟踪错误的转换。

二、C++强制类型转换

标准 C++ 为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:
static_cast、reinterpret_cast、const_cast、dynamic_cast

1. static_cast

static_cast 用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用 static_cast,但它不能用于两个不相关的类型进行转换。它对应的是C语言的隐式类型转换。

例如,相近的类型可以用 static_cast,即意义相似的类型,如下:

				int main()
				{
					double d = 12.34;
					int a = static_cast<int>(d);
					cout << a << endl;
				
					return 0;
				}

但是有一定关联,意义不相似的类型不可以用 static_cast,例如:

				int* ptr = static_cast<int*>(a);

以上语句会发生报错;那么以上语句应该用什么呢?我们往下看。

2. reinterpret_cast

reinterpret_cast 操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型。

像上面这种意义不相似的类型我们应该使用 reinterpret_cast,例如:

				int main()
				{
					int a = 12;
					int* ptr = reinterpret_cast<int*>(a);
				
					return 0;
				}

3. const_cast

const_cast 最常用的用途就是删除变量的 const 属性,方便赋值。

例如:

				int main()
				{
					const int a = 2;
					int* p = const_cast<int*>(&a);
					*p = 3;
				
					return 0;
				}

但是这里有一个奇怪的现象,我们将 a 的值和 *p 的值打印出来,并且将它们的地址打印出来观察:

我们会发现,a 和 p 的地址是一样的,但是当我们修改 *p 的时候,a 的值为什么不变呢?

其实这里和编译器的优化有关系,const 修饰的变量,编译器通常会对它进行优化,它通常会认为 const 修饰的变量不会被修改,所以编译器不会每次都去内存去取数据,它会将数据放在寄存器,甚至用一个常量去替代,类似于宏一样,当我们需要打印数据时,就直接用初始数据替代我们的 const 变量;所以当我们内存中的数据被修改了,但是编译器没有去内存中去取数据,所以 a 的值没有受影响。

如果我们想让编译器每次都去内存中去取数据呢?我们可以使用关键字 volatile,我们在 const 变量前加上这个关键字,就是告诉编译器不需要对该 const 变量进行优化,每次都去内存中取数据,如下:

				int main()
				{
					volatile const int a = 2;
					int* p = const_cast<int*>(&a);
					*p = 3;
				
					cout << a << endl;
					cout << *p << endl;
				
					cout << &a << endl;
					cout << p << endl;
				
					return 0;
				}

我们可以看到 a 和 *p 的值就一样了。但是我们又发现了另外一个问题,为什么 &a 的值是 1 呢?这是因为 cout&a 识别的时候匹配错了,我们只需要将 &a 强转成如下即可:

如果以上的转换我们使用C语言的强制类型转换可以吗?我们可以尝试一下:

				int main()
				{
					const int a = 2;
					int* p = (int*)&a;
					*p = 3;
				
					cout << a << endl;
					cout << *p << endl;
				
					cout << &a << endl;
					cout << p << endl;
				
					return 0;
				}

如上图,也是可以完成转换的。那么C++为什么要使用这几种类型转换的方式呢?其实C++是为了增强程序的可读性,为了将它们区分开来,例如意义相类似的就用 static_cast;意义不相似的就用 reinterpret_castconst_cast 就说明这个类型转换不安全。

4. dynamic_cast

dynamic_cast 用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换),这个是C语言不具备的。

  • 向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)

怎么理解向上转换呢?假设有一个父类 A,一个子类 B,以下场景是不需要转换的,因为符合赋值兼容规则:

				int main()
				{
					B objb;
					A obja = objb;
					A& ra = objb;
				
					return 0;
				}

其中 A& ra = objb; ra 引用的是objb 中父类的部分,即发生了切割,ra 就是 objb 中父类的部分的别名。

  • 向下转型:父类对象指针/引用->子类指针/引用(用 dynamic_cast 转型是安全的)

向下转换的规则:父类对象不能转换成子类对象,但是父类指针和引用可以转换成子类指针和引用。

如果我们直接使用强制类型进行向下转换,是不安全的,例如以下场景:

有两个类,分别是父类和子类:

				class A
				{
				public:
					virtual void f(){}
					int _a = 1;
				};
				
				class B : public A
				{
				public:
					int _b = 2;
				};

当我们分别定义两个类的对象,传给 func 函数:

				void func(A* pa)
				{
					B* ptr = (B*)pa;
					ptr->_a++;
					ptr->_b++;
				}
				
				int main()
				{
					A a;
					B b;
					func(&a);
					func(&b);
				
					return 0;
				}

如果是 func(&b); 那么在 func 函数内就是将父类的对象重新转换为子类,是没有问题的,因为在传入前它本身就是子类的对象。

但是如果是 func(&a); 就会存在越界问题,因为在传入时是父类的对象,在 func 函数内部将该父类对象强制转换成子类对象,那么它本身是父类对象,现在强转为子类对象后,它就可以访问不属于自己的空间 _b,也就是越界访问了,所以是存在问题的。

所以说向下转换直接进行转换是不安全的!

所以C++提供了一种安全的类型转换方式:dynamic_cast,我们可以使用 dynamic_cast 对上面的代码进行修改:

				void func(A* pa)
				{
					B* ptr = dynamic_cast<B*>(pa);
					if (ptr)
					{
						cout << "转换成功" << endl;
						ptr->_a++;
						ptr->_b++;
					}
					else
					{
						cout << "转换失败" << endl;
					}
				}
				
				int main()
				{
					A a;
					B b;
					func(&a);
					func(&b);
				
					return 0;
				}

其中,dynamic_cast 会自动帮我们识别它之前是父类的对象还是子类的对象,从而帮我们实现转换,如果它之前是父类,现在转换为子类,那么就是不可以的,会转换失败,转换失败会返回空;如果它之前是子类,变成父类后又转换为子类,是可以的,就帮我们进行转换。dynamic_cast 还需要一个前提,就是父类必须要有虚函数。

对上面的代码进行测试,当传入父类的对象,转换失败:

当传入子类的对象,转换成功:

总结:

  1. dynamic_cast 只能用于父类含有虚函数的类
  2. dynamic_cast 会先检查是否能转换成功,能成功则转换,不能则返回 0.

版权声明:本文为博主作者:YoungMLet原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/YoungMLet/article/details/135561168

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
乘风的头像乘风管理团队
上一篇 2024年2月19日
下一篇 2024年2月19日

相关推荐