C++实例讲解四种类型转换的使用
目录
- C++类型转换
- C语言风格的转换
- C++风格的类型转换
- static_cast
- reinterpret_cast
- const_cast
- dynamic_cast
- 小结
C++类型转换
C语言风格的转换
C语言提供了自己的一套转换规则,有好处也有坏处。
C语言的风格:(type_name)expression;C语言提供了隐式类型转换和显式类型转换。显式类型转换一般也叫做强转,隐式类型转换编译器完成,如果转换不了就报错。
而C语言类型转换的风格好处就是简单,缺陷比如转换的可视性差,显式类型转换的写法就只有一种,难以精准的跟踪错误。
char ch=1.1,char ch=1在.cpp下都是合法的,这就是隐式类型转换,C语言下如果把一个结构体给int就会报错,因为编译器不知道怎么去转,C++下可以通过实现operator int()实现类型转换或者提供合适的构造函数完成隐式类型转换,下面以在.cpp中实现operator int()为例
文章图片
下面这段代码由于隐式类型转换导致了死循环
文章图片
C语言的类型转换其实已经足够完成需求了,但是可视性不太好,比如你不能在代码库中搜索它(就一对括号怎么去搜索),所以C++提供了一套类型转换,相当于语法糖了,此外还会进行一些编译性检查(比如dynamic_cast转换失败则返回空指针)。但其实作用都是一样的。
C++风格的类型转换
C++标准为了增加类型转换的可见性,提供了四种类型转化的方式。
static_cast 静态类型转换,进行相关类型的转换,但不能转换两个不相关的类型(即编译器看到这个转换是行不通的就会报错)。
static_cast < type-id > ( expression )
int main(){ double a = (int)5.5 + 5.3; //结果是10.3 double b = static_cast(5.5) + 5.3; cout << "a: " << a << endl; cout << "b: " << b << endl; return 0; }
文章图片
转换不相关的类型:
文章图片
reinterpret_cast 很暴力的一个操作符,由英文直译过来就是重新解释的转型。白话就是将一段内存重新解释。
网上看到很多大佬的理解,这里借用或掺杂自己的思想:static_cast是做类型能做的转换,不行编译器就报错,告诉你这样是不合理的,reinterpret_cast则是就算不能转换编译器你也别报错,我心里有数。由于reinterpret_cast本质上是一个编译器指令,所以实际动作完全取决于编译器,失去了移植性。
暴力归暴力,但也是合理范围内的,比如你把一个结构体硬塞给int肯定是不行的,但是把结构体指针重新解释为int那是一点问题没有。
此外reinterpret_cast重新解释的办法是把那一块内存的比特位全部复制下来重新解释。
下面的例子把一个结构体指针解释为int再加上5.3。
struct Test{ int a; }; int main(){ Test* t = new Test; double c = reinterpret_cast(&t) + 5.3; cout << c << endl; return 0; }
文章图片
const_cast 【C++实例讲解四种类型转换的使用】删除变量的const属性,一般和指针或引用连用
int main(){ const int a = 1; //a是常变量,在栈上 int* p = const_cast(&a); *p = 2; cout << a << endl; cout << *p << endl; return 0; }
文章图片
volatile const int a=1; //打印的a就会是2volatile后编译器对访问该变量的代码不再优化。比如上面那句代码打印1就是因为编译器优化,读取的a是从寄存器中读取的,而不是内存。
多线程中也有很多关于 volatile的应用。
dynamic_cast dynamic_cast,安全的向下转型。
多态的转换中向下转型(父类转为子类)用dynamic是安全的,但是父类必须有虚函数,否则编译报错,且只能用于指针或引用。
向上转型,子类给父类,发生切割,不需要转换。
向下转型,父类地址给子类指针,需要类型转换,由于是类型是子类指针,但是给的地址是父类的,如果用指针去访问子类独有的数据可能就会造成越界,然后程序就可能崩了,所以此时就需要dynamic_cast进行转换了,因为dynamic_cast会检查父类指针是否指向的是这个子类,如果是父类说明可能带来一些安全问题,会转换失败返回空指针,反之则实现转换。
理解向下转型不安全有个例子:男人是人,但人不一定是男人(男人派生自人,男人的指针=(男人类型的指针强转)人的地址,此时如果通过男人指针去调用男人自己独有的特性就是越界,因为实际上拿到的是人的地址,并没有存储男人的特性,也可以从空间大小上来解释,人是父类,空间更小,男人是子类,空间更大,给的是小空间,却有了访问大空间的能力,所以可能造成越界,所以是不安全的)
文章图片
此外在一个父类有多个子类且需要类型转换时,dynamic_cast也能起到作用,举个例子
//父类是Entity,子类是Player和Enemy, e,player,enemy分别表示他们对应的实例化对象Entity* p=&player; Enemy* p1=(Enemy*)p; //向下转型需要强转,这样搞就出问题了,p明明指向的是一个player,现在类型为Enemy的指针居然指向了一个Player的对象,如果对其内存进行了操作,那后果是不可预料的。
父类指针一开始指向一个父类对象,再给到子类类型的指针,存在越界的风险,如果为了保证安全则需要检查。
父类的指针指向子类对象(向上转型),再给到子类指针,合理。
所以建议使用dynamic_cast,因为可以保证安全, 转换失败返回空指针(NULL)。
dynamic_cast的底层与RTTI有关,借助RTTI拿到类型信息,所以dynamic_cast更像一个函数,因为不是编译指令,所以会带来一些性能的损失。
RTTI 是“Runtime Type Information”的缩写,意思是运行时类型信息。RTTI存储了所有类型运行时的类型信息,增加了开销但是可以让我们做更多的操作。dynamic_cast的底层就是借助了RTTI+匹配,具体的不太了解。VS可以关闭RTTI,但是也就不能用dynamic_cast了
下面给一个代码例子
class Entity{public: virtual void Print() {}; //dynamic_cast使用的前提必须是父类有虚函数}; class Player:public Entity{}; class Enemy :public Entity{}; void Check(void* p){ if (p) {cout << "转换成功" << endl; } else {cout << "转换失败" << endl; }}int main(){ Entity* e = new Entity(); Entity* actuallyPlayer = new Player(); //向上转型 //安全的向下转型 Player* player =(Player*)actuallyPlayer; //向下转型需要转换类型 Player* player2 = dynamic_cast(actuallyPlayer); Check(player2); cout << endl; //不安全的向下转型 Player* player3 = (Player*)e; Player* player4 = dynamic_cast(e); Check(player4); cout << endl; Entity* actuallyEnemy = new Enemy(); Player* player5 = dynamic_cast(actuallyEnemy); Check(player5); cout << endl; return 0; }
文章图片
dynamic也常常利用返回值是否是空指针来判断指针具体指向谁。
用dynamic_cast的地方其实也可以用static_cast,不过static_cast不会进行安全检查,如果你很清楚其指向,并且在安全的前提下,为了减少程序开销那可以考虑用static_cast
小结
- static_cast用于相关类型的转换
- reinterpret_cast用于重新解释内存(很暴力,用的时候心里要有数)
- const_cast常用来取出const属性,常与指针连用来修改const变量的值
- dynamic_cast,在多态里提供安全的向下转换(转换不安全就返回空指针,只要对返回的指针进行判断我们就能知道此次转换安不安全了)
推荐阅读
- C++超详细讲解强制类型转换的用法
- 属性(property)和实例变量
- 【用户文章】P4合并实践指南之实例拆解Resolve
- java调用加密机实例_Enigma Java模拟实现恩尼格玛密码机加密解密过程 Develop 238万源代码下载- www.pudn.com...
- vue实现多级菜单效果
- C++|DAY2 OpenCv学习笔记( C++ 透视变换)
- js实现图片查看器
- JS数组中filter方法的使用实例
- C++适用入门同学的模板讲解
- C++通用动态抽象工厂的实现详解