上海古都建筑设计集团,上海办公室装修设计公司,上海装修公司高质量的内容分享社区,上海装修公司我们不是内容生产者,我们只是上海办公室装修设计公司内容的搬运工平台

【C++】类型转换

guduadmin493月前

类型转换

  • 一、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(d);
      					cout << a << endl;
      				
      					return 0;
      				}
      

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

      				int* ptr = static_cast(a);
      

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

      2. reinterpret_cast

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

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

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

      3. const_cast

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

      例如:

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

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

      【C++】类型转换,在这里插入图片描述,第1张

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

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

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

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

      【C++】类型转换,在这里插入图片描述,第2张

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

      【C++】类型转换,在这里插入图片描述,第3张

      如果以上的转换我们使用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++】类型转换,在这里插入图片描述,第4张

      如上图,也是可以完成转换的。那么C++为什么要使用这几种类型转换的方式呢?其实C++是为了增强程序的可读性,为了将它们区分开来,例如意义相类似的就用 static_cast;意义不相似的就用 reinterpret_cast;const_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(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 还需要一个前提,就是父类必须要有虚函数。

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

          【C++】类型转换,在这里插入图片描述,第5张

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

          【C++】类型转换,在这里插入图片描述,第6张

          总结:

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

网友评论

搜索
最新文章
热门文章
热门标签
 
 2345周公解梦大全 原版  女人梦见大海很美  带仙缘的人梦境大全