强制类型转换-不存在的东西

    为什么说强制类型转换是不存在的东西呢?因为强制类型转换不会修改原对象的值,只会产生新的对象,并填充新对象的值。
  记得毕业前一次面试,面试官问我C/C++编程中的一个对象为什么会有对象类型这种属性?
    我心里想,一个对象,比如一个int,占4个字节,这4个字节内的数据按照CPU指定的顺序存储为大端或者小端,CPU给这4个字节的区域赋予了一种属性,使它按照int的格式存储而不是unsigned int或其他,使它支持加减乘除求余等操作。当时一下子就懵逼了,没有反应过来,为什么CPU会认定这4个字节是int这种类型而不是其他。
    我突然想起了以前看的《深入理解C++对象模型》,里面讲述过,使用了虚函数的类拥有一个虚函数表指针指向一个用来存储虚函数调用地址的虚函数表,而虚函数表的第0项存的就是关于类型的信息,我立刻就这样回答了。面试官很执着的要把我绕进坑里,他就说没有使用虚函数的类呢?比如int这些基础类型呢?
    我心想,虽然不知道答案,但是应该是同理的吧,就回答,那肯定是程序里有记录啊。记录每个对象的类型?记在哪里?那岂不是要花很多的存储空间咯?我:......可能记在可执行文件的某个段里吧,心里想,这么重要的问题,我以前怎么没有关注过。我接着说,不知道,反正肯定是有记录的。。。
    很神奇,面试这么多次,第一次碰到这种问题,回来后我就查了一下。恍然明白,这么简单的道理,被面试官把我引导进一个大坑了。
    C/C++根本不记录每个对象的类型信息,除了使用了多态的类型对象(虚函数表里有,用来支持RTTI)。C/C++只是在编译器编译程序的时候检查一下类型信息,检查每一个内存块(及对象),有没有对这个对象做了这个对象类型不支持的操作。如果有,则编译不通过,如果编译通过了,那说明全部操作都是合法的,则实际编译后的结果-可执行程序是不包含它们的类型信息的。也就是说程序执行的时候根本不知道哪块内存块是哪种类型,而完全不会出现错误的操作,是因为编译期已经检查过此程序将来运行时不会出现错误的操作,此处错误的操作指的是把一个操作用在了一个不支持这种操作的内存块上,这种错误的保障在编译期就已经确定了,不会出错,如果可能存在这种错误,则编译不通过。把int放到了float寄存器中,这是不可能出现的汇编语句,如果有这种情况,则编译不能通过,如果编译通过了,把int对象放到float寄存器也是可以的,因为运行的时候根本不知道每块内存的类型。
    而今天要说的,是有的时候,我要把int类型的对象当成int*类型的对象使用,结果编译过不去,这时候我明白我的程序能这样做,但是编译器就是不让这么做,我只要让编译通过了,我心里有底它运行时不会出错,怎么过编译器这一关呢?C/C++编译时非要检查每个对象的类型和操作是否匹配,幸好有类型转换着中东西,这就是人们说的C/C++是弱类型、静态类型的语言。而且好像只有C/C++语言既是静态类型又是弱类型,其他语言都没这样。比如java也是静态类型语言,但是它不支持类型转换,属于静态类型的强类型语言。而作为弱类型语言的C/C++,类型之间可以随便转换,只要你不怕运行时出错。
    那就是强制类型转换,一般来说正常的程序不应该存在类型转换,但是实际编程中我总是遇到这样的问题,可能是程序设计水平不够吧。所以我要说的是,强制类型转换是不存在的,强制类型转换只是为了让我们能把程序编译通过,别无它用。为了程序编译通过?有人肯定要笑了,你怎么这么菜,学了这么久的程序了,写个程序还编译都过不去,我以前也担心有人这样说我,但是后来工作中我发现,想让程序一次性能够编译通过绝非一件容易的事,每次对程序作了修改后,遇到的第一件事就是怎么让它编译通过。哪些情况会编译过不去呢?很多,例如:类型错误,比如你用int*类型的对象求余,理论上是可以的,但是编译器就是不让你这么做,因为指针类型不支持求余;链接错误:找不到静态库,由于编译器编译时输入的参数不对,导致找不到静态库中的函数定义发生连接错误;重复定义:一个函数定义了多次等问题;找不到头文件,引用了那么多的开源库,头文件包含路径也得好好配置;语法错误:打错字等;。。。
    编译通过不是一件简单的事,只要编译通过了,后面程序出问题了我们可以使用GDB等一些调试工具来调试程序,或者打日志、注释代码找bug,coredump文件查看程序运行堆栈等。
    讲了这么多,我才进入正题,类型转换,类型转换是很神奇的东西,就在昨天,我在公司系统联调的时候,同事发现了我的代码中,把std::string类型的对象强制转成一个枚举类型。。。居然编译运行都没问题,问题是它没有按照我们的逻辑运行被我们发现了,挺尴尬的。其实我是用强制类型转换,想把uint32_t类型转成枚举类型,结果错把std::string类型转成了枚举。强制类型转换厉害吧,这样就骗过了编译器,只要骗过了编译器,就能运行,先不论后面引发的BUG。
    C语言中提供了几种类型转换,首先是隐式转换,也叫做类型提升,比如int i = 0; float f = i;自动把i提升成float类型再赋值给f。这种隐式转换到处存在,但是作为程序员的我们,需要明白哪些地方发生了这种隐式转换。而如果反过来把float对象转成int对象,则编译器会提出警告信息,这时候就要注意了,不然可能在运行时引发错误,如果你写了float f = 1.1; int i =f;这种情况运行时就造成了精度损失,f转成int类型对象时不可能还能有小数。这一定点精度损失可是很危险的,当初一次double类型对象转成float对象都导致了火箭发射失败,损失很多个亿。我们应当尽量避免这种隐式类型转换,因为可能今天你记得这行代码发生了隐式转换,明天可能就忘了,也可能你的代码给别人用的时候别人不知道这里有隐式转换的坑。最好把隐式转换都改成显示转换,至少代码中有转换类型的痕迹,代码是变多了一些,但是运行出错了至少还能找到可能出错的地方。
    C语言中的强制类型转换:
    int i; (int*)i;
    int i; int*(i);
    直接在对象前面加上想转的目标类型,然后用个括号分割一下就行,发生强制类型。
    基础类型之间的转换,不管是强制类型转换还是隐式的类型提升,都不会改变原来对象的内存块里的内容,这是很安全的,所有的类型转换,都会在转换的瞬间构造一个目标类型的临时对象,看上去就像临时对象的构造函数接受了原类型对象当做参数,构造出目标类型的对象,然后再用这个临时对象做类型转换后的事情。至于内容填充时做的内容转换,就得看看C语言的规定了。C++类定义中甚至可以重载定义两种类型的隐式转换和显示转换。
    在C++中,兼容了C语言的隐式类型提升和强制类型转换方法(使用括号),另外还定义了四种强制类型转换的方法,但是原理上都是构造目标类型对象然后填充使用,不改变原对象的内存内容。类型转换并不修改已经定义的对象的类型,而是将一种类型对象赋值给另一种类型对象。

type val = static_cast<type>( expression); 强制将一种数据类型转换为另一种数据类型,类似于C语言的强制转换,构造type类型的临时对象,使用expression填充,然后返回此临时对象供使用。万能的强制转换。

type val = const_cast<type>( expression); 用于强制去掉这种不能被修改的常数特性(const、volatile),不是用于去除变量的常量性,而是去除指向常数对象的指针或引用的常量性,其去除常量性的对象必须为指针或引用。输入一个const type*p(或type const* p,指向常量的指针,而不是type* const p(p指针本身是一个常量)),返回一个指向变量的指针(所指向的内容可以修改)。由于指向常量的指针值不能赋给指向变量的指针,所以在这种尴尬的时候需要这样做。也可以通过两次强制类型转换实现。 除了const 或volatile修饰之外, type和expression的类型是一样的。

reinterpret_cast<type>( expression); 改变指针或引用的类型、将指针或引用转换为一个足够长度的整形、将整型转换为指针或引用类型

dynamic_cast<type>( expression); 其他三种都是编译时完成的,dynamic_cast是运行时处理的,运行时要进行类型检查,基于多态中虚函数表第0项的对象类型信息实现。 转换失败的话则会返回NULL。 子类指针转成其父类指针能成功。父类指针转成子类指针时,检查原指针指向的对象的实际类型与目标指针类型是否相同,不相同则转换失败。

 
注意:最后一种类型转换是C++新增加的,只有在多态情况下才能用到的,运行时类型识别,如果你怕子类指针指向父类对象,则可以使用dynamic_cast来防止。
原文地址:https://www.cnblogs.com/xjjsk/p/9239862.html