C++ 四种cast 详解
一.cast出现的意义
1.C++继承并扩展C语言的传统类型转换方式,提供了功能更加强大的转型机制(检查与风险)
2.更好的定位转型的地方(ctrl+F cast)
二.reinterpret_cast
reinterpret_cast是四种强制转换中功能最为强大的(最暴力,最底层,最不安全)。它的本质是编译器的指令。
它的作用:它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针。或者不同类型的指针的相互替换
代码示例:
#include <iostream>
int main()
{double a = 1.1;char * c = reinterpret_cast<char*>(&a);double* b = reinterpret_cast<double*>(c);printf("%lf",*b);
}
运行结果:
分析:我们通过中间的 char*来转double但是没有出现精度问题。事实上reinterpret_cast只是在编译器进行的予以转化,只要是个地址就可以转(二进制拷贝)。
三.const_cast
有两个功能,去掉const和加上const
加上const的情况:
代码:
#include <iostream>
int main()
{int* a=new int(1);const int * b = const_cast<const int * >(a);*a=2;//*b=2;,常量不能修改printf("%d\n",*a);printf("%d\n",*b);
}
运行结果:
分析:
我们发现值是一样的,但是地址不一样,说明不是简单的转型而是先深拷贝在去转型
去掉const的情况(这里的情况非常多,深拷贝和浅拷贝各不一样,转型之后返回的可能不是原地址):
代码:
#include <iostream>
class A
{public:int num;A(int val = 100):num(val){}~A(){}
};
int main()
{//1.const 修饰指针对象,指向原对象 const A * pa1 = new A(200);A * cast_pa1 = const_cast<A * >(pa1);printf("1.const 修饰指针指向对象\n");printf("%p\n",pa1); printf("%p\n",cast_pa1); //2.const 修饰指向指针对象的值,指向原对象A * const pa2 = new A(200);A * cast_pa2 = const_cast<A * >(pa2);printf("2.const 修饰指向对象的值\n");printf("%p\n",pa2); printf("%p\n",cast_pa2);//3.const 同时修饰指针对象和指针对象的值,指向原对象const A * const pa3 = new A(200);A * cast_pa3_1 = const_cast<A * >(pa3);const A * cast_pa3_2 = const_cast<A * >(pa3);A * const cast_pa3_3 = const_cast<A * >(pa3);printf("3.const 同时修饰指针对象和指针对象的值,指向原对象\n");printf("%p\n",pa3); printf("%p\n",cast_pa3_1);printf("%p\n",cast_pa3_2);printf("%p\n",cast_pa3_3);//4.const 修饰普通对象,并且赋值给一般对象,不指向原对象 const A pa4;A cast_pa4 = const_cast<A &>(pa4);printf("4.const 修饰普通对象,并且赋值给一般对象\n");printf("%p\n",&pa4); printf("%p\n",&cast_pa4);//5.const 修饰普通对象,并且赋值给引用对象,指向原对象const A pa5;A& cast_pa5 = const_cast<A& >(pa5);printf("5.const 修饰普通对象,并且赋值给引用对象\n");printf("%p\n",&pa5); printf("%p\n",&cast_pa5);// 6. const 修饰对象,对象指针去 const 属性后赋给指针,指向原对象 const A pa6;A * cast_pa6 = const_cast<A * >(&pa6); printf("6. const 修饰对象,对象指针去 const 属性后赋给指针\n");printf("%p\n",&pa6); printf("%p\n",cast_pa6);//7.const修饰局部变量,不指向原对象const int pa7=1;int cast_pa7_1 = const_cast<int&>(pa7); int& cast_pa7_2 = const_cast<int&>(pa7);int* cast_pa7_3 = const_cast<int*>(&pa7);printf("6. const 修饰对象,对象指针去 const 属性后赋给指针\n");printf("%p\n",&pa7); printf("%p\n",&cast_pa7_1);printf("%p\n",&cast_pa7_2);printf("%p\n",cast_pa7_3);cast_pa7_1=10;printf("%d,未修改\n",pa7);cast_pa7_2=100;printf("%d,未修改\n",pa7);*cast_pa7_3=1000;printf("%d,未修改\n",pa7);
}
执行结果:
分析:
去掉对象指针的const,全是原对象
去掉一般对象的const,如果赋值给一般对象则是新对象,否则全是原对象
去掉局部变量的const,全是新对象
四.static_cast
作用:
1.基本类型之间的转换
2.void指针转换为任意基本类型的指针
3.用于有继承关系的子类与父类之间的指针或引用的转换
基本类型之间的转换:
代码:
#include <iostream>
int main()
{double i=1.1;int a = static_cast<int>(i);printf("%d\n",a); double b = static_cast<int>(a);printf("%lf\n",b);
}
运行结果:
分析:可以进行基本类型的转化,但是会损失精度类似与C语言的强制转化。跟reinterpret_cast不太一样reinterpret_cast是底层二进制的强制拷贝和语义转换不会损失精度。
void指针和其他指针的转换;
代码;
#include <iostream>
int main()
{int *a = new int(1);void *v = static_cast<void *>(a);int *p = static_cast<int *>(v);*a=2;printf("%d\n",*a);printf("%d\n",*p);printf("%p\n",a); printf("%p\n",p);
}
分析:这里是void指针和其他类型的指针进行的转化,结果是指向的是原地址。(普通类型的转换不是)
子类和父类之间的转换:
代码:
#include <iostream>
using namespace std;
class A
{public:A(){}void foo(){cout<<"A!"<<endl;}
};
class B:public A
{public:B(){} void foo(){cout<<"B!"<<endl;}
};
int main()
{A *a = new A();B * b = static_cast<B *>(a);b->foo();return 0;
}
运行结果:
这是向下转型,是不安全的,但是为什么没有报错呢,因为B中还没有B特有的(B的成员变量)。我们在看看别的
代码:
#include <iostream>
using namespace std;
class A
{public:A(){}void foo(){cout<<"A!"<<endl;}
};
class B:public A
{char b='c';public:B(){} void foo(){cout<<b<<endl;}
};
int main()
{A * a = new A();a->foo();static_cast<B*>(a)->foo();return 0;
}
运行结果;
分析:这里就发生了错误了,B中特有的成员变量没有初始化(使用了不安全的向下转型)
static_cast的类的转型类似于普通的强转,可以抛出异常
五.dynamic_cast
dynamic_cast用于类继承层次间的指针或引用转换(主要用于向下的安全转换)
dynamic_cast向下转型的安全性主要体现在RTTI
RTTI:
运行时类型识别。程序能够使用基类的指针或引用来检查着这些指针或引用所指的对象的实际派生类型(判断指针原型)
RTTI提供了两个非常有用的操作符:typeid和dynamic_cast。(三个最主要的东西,dynamic_cast,typeid,type_info)
typeid:typeid函数(为type_info类的友元函数,为什么要这样呢?目的是防止创建type_info对象)的主要作用就是让用户知道当前的变量是什么类型的,它可以返回一个type_info的引用,可以获取类的名称和编码typeid重载了type_info中的==和!=可以用于判断两个类型是否相等
1)typeid识别静态类型
当typeid中的操作数是如下情况之一时,typeid运算符指出操作数的静态类型,即编译时的类型。
(1)类型名
(2)一个基本类型的变量
(3)一个具体的对象(非指针对象)
(4)一个指向 不含有virtual函数的类 对象的指针的解引用
(5)一个指向 不含有virtual函数的类 对象的引用
静态类型在程序的运行过程中并不会改变,所以并不需要在程序运行时计算类型,在编译时就能根据操作数的静态类型,推导出其类型信息。例如如下的代码片断,typeid中的操作数均为静态类型:
代码:
#include <iostream>
#include <typeinfo>
using namespace std;
class X {public:X(){}void func(){}
};
class XX : public X {public:XX(){}void func(){}
};
class Y {public:Y(){}void func(){}
}; int main()
{int n = 0;XX xx;Y y;Y *py = &y;// int和XX都是类型名cout << typeid(int).name() << endl;cout << typeid(XX).name() << endl;// n为基本变量cout << typeid(n).name() << endl;// xx所属的类虽然存在virtual,但是xx为一个具体的对象cout << typeid(xx).name() << endl;// py为一个指针,属于基本类型cout << typeid(py).name() << endl;// py指向的Y的对象,但是类Y不存在virtual函数cout << typeid(*py).name() << endl;return 0;
}
2)typeid识别多态类型
当typeid中的操作数是如下情况之一时,typeid运算符需要在程序运行时计算类型,因为其其操作数的类型在编译时期是不能被确定的。
(1)一个指向含有virtual函数的类对象的指针的解引用
(2)一个指向含有virtual函数的类对象的引用
代码:
#include <iostream>
#include <typeinfo>
using namespace std;
class X
{public:X(){mX = 101;}virtual void vfunc(){cout << "X::vfunc()" << endl;}private:int mX;
};
class XX : public X
{public:XX():X(){mXX = 1001;}virtual void vfunc(){cout << "XX::vfunc()" << endl;}private:int mXX;
};
void printTypeInfo(const X *px)
{cout << "typeid(px) -> " << typeid(px).name() << endl;cout << "typeid(*px) -> " << typeid(*px).name() << endl;
}
int main()
{X x;XX xx;printTypeInfo(&x);printTypeInfo(&xx);return 0;
}
运行结果:
最后真实的判断出了指针原型
那么问题来了,typeid是如何计算这个类型信息的呢?下面将重点说明这个问题。
多态类型是通过在类中声明一个或多个virtual函数来区分的。因为在C++中,一个具备多态性质的类,正是内含直接声明或继承而来的virtual函数。多态类的对象的类型信息保存在虚函数表的索引的-1的项中,该项是一个type_info对象的地址,该type_info对象保存着该对象对应的类型信息,每个类(多态)都对应着一个type_info对象
在多重继承和虚拟继承的情况下,一个类有n(n>1)个虚函数表,该类的对象也有n个vptr,分别指向这些虚函数表,但是一个类的所有的虚函数表的索引为-1的项的值(type_info对象的地址)都是相等的,即它们都指向同一个type_info对象,这样就实现了无论使用了哪一个基类的指针或引用指向其派生类的对象,都能通过相应的虚函数表获取到相同的type_info对象,从而得到相同的类型信息。
dynamic_cast(可以抛出异常)
dynamic_cast借助RTTI机制实现了安全的向下转型(无法转型的返回NULL)
代码:
#include <iostream>
#include <typeinfo>
using namespace std;
class X
{public:X(){mX = 101;}virtual ~X(){}private:int mX;
};class XX : public X
{public:XX():X(){mXX = 1001;}virtual ~XX(){}private:int mXX;
};class YX : public X
{public:YX(){mYX = 1002;}virtual ~YX(){}private:int mYX;
};
int main()
{X x;XX xx;YX yx;X *px = &xx;cout << px << endl;XX *pxx = dynamic_cast<XX*>(px); // 转换1cout << pxx << endl;YX *pyx = dynamic_cast<YX*>(px); // 转换2cout << pyx << endl;pyx = (YX*)px; // 转换3cout << pyx << endl;pyx = static_cast<YX*>(px); // 转换4cout << pyx << endl;return 0;
}
运行结果:
分析:
px是一个基类(X)的指针,但是它指向了派生类XX的一个对象。在转换1中,转换成功,因为px指向的对象确实为XX的对象。在转换2中,转换失败,因为px指向的对象并不是一个YX对象,此时dymanic_cast返回NULL。转换3为C风格的类型转换而转换4使用的是C++中的静态类型转换,它们均能成功转换,但是这个对象实际上并不是一个YX的对象,所以在转换3和转换4中,若继续通过指针使用该对象必然会导致错误,所以这个转换是不安全的。
声明:引用的情况与指针稍有不同,失败时并不是返回NULL,而是抛出一个bad_cast异常,因为引用不能参考NULL。
C++ 四种cast 详解相关推荐
- kinux查日志_Linux实时查看日志的四种命令详解
原标题:Linux实时查看日志的四种命令详解 如何在Linux中实时查看日志文件的内容?那么有很多实用程序可以帮助用户在文件更改或不断更新时输出文件的内容.在Linux中实时显示文件内容的常用命令是t ...
- js和php能生成一样的随机数_JavaScript_JS生成某个范围的随机数【四种情况详解】,前言:
JS没有现成的函数,能 - phpStudy...
JS生成某个范围的随机数[四种情况详解] 前言: JS没有现成的函数,能够直接生成指定范围的随机数. 但是它有个函数:Math.random() 这个函数可以生成 [0,1) 的一个随机数. 利用它 ...
- python输入字符串并反序result_python字符串反转的四种方法详解
python字符串反转的四种方法详解 这篇文章主要介绍了python字符串反转的四种详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.用red ...
- RTSP、HTTP、HTTPS、SDP四种协议详解
RTSP.HTTP.HTTPS.SDP四种协议详解 从这篇开始我们将进入流媒体的环节,流媒体在android中有nuplayer来实现的,在开始讲解android流媒体前,我们先来讲讲流媒体传输协议, ...
- 内部类(四种内部类详解)
== = = = = = = 内部类(四种内部类详解)= = = = = = = == 一.基本介绍:一个类的内部又完整的嵌套了另一个类结构.被嵌套的类称为内部类(inner class),嵌套其他类 ...
- java正则表达式判断问号_正则表达式问号的四种用法详解
原文符号 因为?在正则表达式中有特殊的含义,所以如果想匹配?本身,则需要转义,\? 有无量词 问号可以表示重复前面内容的0次或一次,也就是要么不出现,要么出现一次. 非贪婪匹配 贪婪匹配 在满足匹配时 ...
- php 去重_php求两数组交集的四种方法详解
题目:给定两个数组,编写一个函数来计算它们的交集. 示例 1: 输入: nums1 = [1,2,2,1],nums2 = [2,2] 输出: [2] 示例 2: 输入: nums1 = [4,9,5 ...
- 字符串反转python_python字符串反转的四种方法详解
这篇文章主要介绍了python字符串反转的四种详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.用reduce函数方法 book = 'Pyt ...
- 无线攻击及密码破解的四种方式详解
随着社会的进步,现在我们在每一地方逗留都离不开无线通信,WiFi.4G等等:这就是无线领域的优势所在! 无线领域十分难以捉摸,从一点儿一点儿进步到现在,无线的安全深入人心,站在安全的角度来说无线通信一 ...
最新文章
- java文件读写不完整_Java读取文件内容不完整
- TensorFlow object detection api------ssd_mobilenet使用
- JavaScript text highlighting JQuery plugin
- 开发提效小技巧分享(一)
- Arcgis Server开发使用query报错
- docker学习记录 docker 脚本(一)
- 操纵股价的10种手段
- 共模电压 matlab,SPWM死区对三电平高压变频器共模电压的影响
- JAVA复习总结 一( 详细,干货!)
- xp系统打开internet服务器,WinXP电脑Internet选项打不开的解决方法
- 《读书的力量》读后感作文4000字
- PHP乘法表菜鸟教程,第二节 菜鸟教程的实例
- 万豪国际集团亚太第800家酒店正式开业
- 数据库系列7:事务与锁的实现原理
- VS2015 + QT 编译出现public: virtual struct QMetaObject const * __thiscall 错误
- 程序员的职级和薪酬体系
- SEO快排真的有效吗?什么样的快排才靠谱?
- git clone 出现fatal: unable to access ‘https://github.com/xxx: Failed to connect to github.com
- 并查集之LeetCode1579. 保证图可完全遍历
- iOS文档查看-QuickLook