跟我学C++中级篇——数据类型转换的思考
一、数据类型的转换
可以这样说,几乎所有的开发者都必须经历这种数据类型转换导致的异常的痛苦。不管是C还是C++的开发者,这都是一个必须经历的过程。在多线程开发的早期,传入线程的就是一个void*的指针,然后在线程函数内部自己再做相应的类型转换。有过因此导致线程崩溃的开发者,一定会对此记忆尤新。
什么是数据类型转换?为什么为有数据类型的转换?
数据类型转换,其实就是把一咱类型转换为另外一种数据类型。比如int转double,字符串转int等等。那么有人会问,为什么会出现数据类型的转换,既然它水那么深,不能不进行数据类型的转换么?这个其实非常好回答。举一个现实世界的例子,家里有一小段水管大约5厘米损坏了,你去市场上买,虽人会从一段较长的水管上截取下5厘米给你,而不是直接给一个出厂就5厘米的水管。想吃苹果,就想吃一口,也得买一个。这涉及到成本、交易方式等等各种原因。到计算机的虚拟世界,也是如此。怎么可能开发者想要啥就有啥,只有一些标准的东西是立刻就有 的,就如你要买水龙头、接头或标准长度的水管(有一米、三米、五米以及不同直径等)等标准的五金件,可以直接选择合适的。
比如开发者想得到图的一些元数据,那可能就需要接收一大堆的二进制的字节流,然后再自己组合分析,把相应的元数据转换出来,才能自己使用。如果拿到了一个char类型的数据,但手头的保存类型只有int类型,那么只能转成int。这个都是实际非常容易遇到的情况,还有一些特殊的情况,比如一个结构体,可以截取一部分形成另外一个结构体,来达到自己的目的,这也需要进行数据转换。
二、数据类型转换的情况
常见的数据类型转换主要有以下几种情况:
1、基础类型的转换
这种非常常见,如char和int,float和double等等。
2、有符号无符号
这种一般也比较常见,但很容易让人忽视一些风险,往往导致一些意想不到的后果。
3、类或结构体对象的转换
这种转换是C++中比较常见,虽然C中有结构体,但C中的结构体对象更多的是和其它类型的数据进行转换,如char等。
4、指针的转换
指针本身就是一个大难点,但指针的转换不可预料的风险更高,特别是有些为了兼容考虑的使用void等做为中间转换的更是如此。
5、继承的转换
继承的转换其实就是父子类的转换,包括父子类指针的转换,这种情况往往是单向的安全,即从子类转到父类。但在某些情况下,父类到子类也是安全的,这就需要开发者特别的注意。
三、数据类型转换的问题
之所以讨论这个问题,重要的原因就是数据类型转换的结果,往往会产生一些异想不到的问题。除了明显的可以想到的崩溃、异常退出等问题外,另外一种是比较难于发现的。比如父子类的转换或者有无符号的转换等,它们产生的问题,往往在某些情况下才会暴露出来,而且这种问题一般运行都没有问题,只是偶尔可能得到不想要的结果。
一般来说,在数据类型转换中经常遇到的问题有以下几类:
1、使用C方式的强制转换的安全风险问题
这种方式的缺点就是要求开发者必须明确前后转换的数据类型是否安全,是否会有溢出等风险。比如一个int类型转short类型,就必须明确知道这数据是否会走出short类型的范围。
2、指针的转换导致的异常问题
这里特别指出的就是使用void*做为中间态的指针参数转换,要必须明确了解转换的安全性的前提下进行操作;另外一个就是父子类对象指针,一定要明确内在的运行逻辑,防止出现问题。另外包括一些内存对齐、大小端等的要求都需要考虑(比如网络数据的转换)。
3、编译期通过但运行期可能不正确的问题
这种问题就比较不容易发现了,往往在某个特定场景或特殊操作时才可能出现。它最难缠的不是崩溃,往往是很偶尔出现无法执行正常逻辑。
四、数据类型转换的解决
在现实世界如果遇到不匹配的材料怎么办?看木工,直接粘一块或者截取一块即可。五金比较常见的就是截取一截或者用套件连接一段。其实在程序中,也是这样。最初的C语言中就是暴力转换,它不管你对和错,只要你敢转,它就敢换。至于结果怎么样,谁种下的瓜,谁吃什么样的瓜。所以在早期的程序中,使用拼凑或截取等方法进行强转,风险非常大。所以因为数据类型转换出问题的情况也非常多。
给大家举一个作者亲自的经历,在早期的编程中,有个同事问为啥一段代码的逻辑不对。后来才发现,代码中把有符号和无符号进行了比较,负值变成了非常大的数,所以程序的逻辑自然就错了。就这个类型的错误,在后来的编程过程中遇到的虽然不多,但几乎每两三年就会经历一次。
那么在经历这么多的麻烦和异常后,C/C++的大佬们有没有什么解决办法来控制一下这种问题呢?答案查肯定的(这里不考虑C)。在C++中解决这种转换的方法有:
1、几种cast
它们包括static_cast、dynamic_cast 与 const_cast,但一般不建议使用reinterpret_cast。这几种转换的方法很简单,此处就不做说明了,不明白的简单翻一下书籍即可知道。特别是使用智能指针后,父子类型指针的转换要使用dynamic_pointer_cast会更安全。
2、对象的转换函数(一个参数的构造函数)
这其实就是对象中常见的一种隐式的数据类型转换。比如把一个带int型参数的构造函数的类对象直接赋值一个整数,则会产生这种默认的隐式转换。但是这种一般风险很大,所以需要下面的第四种方式进行控制。
3、C++11的类型转换
在C++11以后,对类对象的转换提供了operator操作符进行转换。具体的方法参看相关资料或前面的文章“c++11的类型转换函数”。
4、隐式转换的控制
在2中提到了隐式转换,它是一种非开发者知晓情况下进行了,所以可能就不是开发者想要的。因此,需要对进行控制,即类对象中要使用explicit来控制单个参数的构造函数,而对于一些基础类型,则需要使用上面提到的cast进行。同时,有必要的话进行其它情况的判定(如溢出)。
强制数据类型转换简单是简单,但它是不考虑后果的。这种方法对于大型程序来说肯定是不友好的,所以在C++中采用其它一些方法是一种必然选择。不过话说回来,开发者是数据类型转换的具体执行者,它的控制能力才是真正的数据类型转换安全的决定者。所以开发者不但自己要身体力行的编写安全的可控的代码,又要让编译器可以参与进来,共同降低数据类型的转换风险。
五、总结
从上面的分析其实可以明白,就是开发者不能简单的相信自己或其它开发者的技术,要善于借助编译器等工具进行安全检查即开发的数据类型转换代码可以由编译器进行类型判别,防止出现异常。要尽量使用显示的数据类型转换,而不是使用不可控的隐式数据类型转换。阳光透明才安全,安全永远是第一位的,这个是常识!
原文地址:https://blog.csdn.net/fpcc/article/details/144308013
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!