《C语言点滴》读书笔记(3)第3章数据类型(关键词:C语言/数据类型)
2017-12-05 15:11
453 查看
第3章 数据类型
3.1 原码、反码和补码的解释
原码:原码是一种计算机中对数字的二进制表示方法,数码序列中最高位为符号位,符号位为0 表示正数,符号位为1表示负数;其余有效值部分用二进制的绝对值表示。反码:如果机器数是正数,则该机器数的反码与原码一样;如果机器数是负数,则该机器数的反码是对它的原码(符号位除外)各位取反而得到的。
补码:如果机器数是正数,则该机器数的补码与原码一样;如果机器数是负数,则该机器数的补码是对它的原码(除符号位外)各位取反,并在末位加1。
(读者:以下描述中提到的图,请参照书上。)
顾名思义,无符号数不能表示负数。为了解决这个问题,我们把最高位定义为符号位。如果最高位为1 就代表是一个负数,其他三位表示对应的十进制数。如图3-3所示,这就是有符号数的原码表示。
用原码来表示一个有符号数会带来两个问题。第一个问题就是正负相加不等于零。如图3-3 所示,1+(-1),就是0001+1001=1010,按照原码表示等于-2。第二个问题就是有两个零存在,分别为0000 和1000。可见,原码不适合用来表示有符号数!
为了保证正负相加等于零,我们采用了反码的表示方法,反码的表示如图3-4 所示。如果把二进制数1000 想象成12 点,把二进制数0000 想象成6 点,原码就是从12 点开始顺时针排列-1到-7,而反码就是从6 点开始逆时针排列-1到-7。这样做的好处就在于现在正负数相加等于零了。例如,1+(-1),就是0001+1110=1111,用反码表示的话就是(-0)了。
第一个问题解决了,但是第二个问题还是没有解决,在用反码表示有符号数的图3-4 中,依然有两个零存在,分别为0000 和1111。聪明的读者一定已经猜到了,补码就是为了解决这个问题而发明的。
按照补码的定义,-1 的反码为1110,不过现在必须在末位加1,那现在-1就是1111了。以此类推,补码的表示如图3-5。与反码表示不一样的是,补码在负数上从-1表示到-8,-0不再存在了。
虽然第二个问题解决了,但用补码表示时,正负相加还等于零吗?我们可以自己验证一下,如果丢弃最高位的进位,结果满足正负相加等于零。至此,我们找到了*终极的解决方案,那就是利用补码来表示一个有符号的整数。注意一点,对于无符号数,原码、反码、补码都是一致的。所以我们得到的最后结论是:整型数在计算机中,使用补码表示。
3.2 整型数的溢出
如果3.1 节你已经完全明白了,那么下面我们开始讨论在实际编写程序中非常危险的一个bug,那就是C 语言的整型数溢出问题。大家不要害怕,有了3.1 节的基础,下面讨论的溢出问题会比较容易理解。首先,我们知道,有符号数在计算机内部都是通过补码来表示的。其次,在图3-5中,如果加上x,就代表着顺时针走x 格;如果减去x,就代表着逆时针走x 格。有了这些知识,让我们用实例说话。在图3-5 中,如果3+1,那就是从3 顺时针走一格,等于4,没有任何问题。但是如果是7+1 呢?顺时针走1 格后,变成了-8了。如果7+7呢,顺时针走7 格,等于-2了。这就是整型数发生了溢出。所谓的溢出,就是因为4位二进制数,用补码表示一个整数的时候,所能表示的最大正整数就是2(4-1)-1 = 7,如果在7 的基础上再加1,就会发生“轻微溢出”,变为了最小的那个负数,如果两个最大的正整数相加,就会发生“严重溢出”。结果等于-2。这里注意,所谓的“轻微溢出”和“严重溢出”都是从溢出的角度去定义的。事实上,它们都是非常严重的bug,在实际编程中并不是说“严重溢出”就比“轻微溢出”更严重。
3.3 溢出深入分析
3.3.1 溢出的定义
3.3.2 溢出的边界
3.3.3 溢出的危害
3.3.4 避免溢出的方法
一个比较简单的避免溢出的方法就是利用double 数据类型。一般情况下,让double 溢出还真是件比较困难的事。如果要求一定要用整型数来完成任务,那么你首先应该利用表3-1 中的宏定义,了解一下C 语言在你所用平台上的极限值,并且利用这些宏定义和程序3-4 中列出的判断表达式来避免溢出的发生。请注意,程序3-4 中的判断表达式有的时候代表溢出会发生,有的时候代表溢出已经发生。3.4 无符号数
前面已经介绍了整数的二进制表示方法,对于n 位二进制数,也就只能表示2n个整数。一个顺其自然的想法就是用这2n个整数的一半来表示正数,另外一半来表示负数。这种表示方法就是有符号数了。但是在实际的应用中,有很多种情况是不会出现负数的。比如说我们的年龄,一个班级的课程数,一个国家的人数等。如果用有符号数来保存这些值,那么永远不会用到表示负数的那一半范围,这样就被白白浪费了,而且还使得正数的表示范围被占用了一半。
针对这个问题,C 语言中引入了无符号数的概念。无符号数的源码、反码、补码都一致,具体的表示方法请参考3.1 节中的介绍。对于n 位二进制数,表示0 到2n-1-1范围内的整数。
注意,无符号这个特性,只适用于整型数,而不适用于浮点数。同时我们一定要注意在表达式中混用有符号和无符号数的情况。这是因为C 语言的表达式中会发生一种比较神奇的隐式数据类型转换,这种隐式的数据转换会带来一些隐含错误,如程序3-5 中所示。
程序3-5 无符号整型数溢出实例
1 int Sum(int a[], unsigned length){ 2 int i = 0; 3 int sum = 0; 4 for(i = 0;i<=length-1 ;i++){ 5 sum+=a[i]; 6 } 7 return sum; 8 } 9 if(-1>0U){ 10 printf("???"); 11 }
程序3-5 中的Sum 函数是一段非常中规中矩的程序,每个细节都考虑得很好。数组的长度 length 不可能是负的,所以声明为unsigned。在程序3-5 第4 行的表达式i<=length-1 中,由于length 是一个无符号整数,整个表达式length-1最后的结果也被隐式转换为无符号类型。这样,
当length=0 的时候,整个表达式变为了 0U-1U,在3.3.2 节中的图3-7 中,下溢出正好对应这种情况。最后造成的结果就是,当length=0 的时候,表达式length-1 的值是最大的无符号数UINT_MAX,这样for 循环就会执行UINT_MAX 次。不过你不用担心,for 循环不会真的执行
UINT_MAX 次,因为它会因为执行非法的内存访问而被操作系统一脚踢出。
与此类似的一个情况如程序3-5 中的第9~11 行所示,这段程序会打印出“???”。因为在表达式中会发生隐式类型转换,所以-1 被转换成了无符号类型。别忘了,-1的二进制表示(全部二进制位都是1)在无符号整数中被定义为UINT_MAX。
无符号数据类型另外的一个主要隐含错误来源于sizeof,我们在3.9 节会给出另外一个实例。本来无符号数是为了能扩大其表示的范围,但是却带来了很多的隐含错误,有点得不偿失了。所以,尽量不要在你的程序中使用无符号类型,以免增加不必要的复杂性,尤其不要仅仅因为无符号类型不存在负数而使用它来表示一个数值。随着计算机机器字长从32 位过渡到64位,一个有符号数据类型int 所表示的范围已经很大了,基本的问题都可以hold 住,别忘了,实在不行还有long long类型。用int 数据类型的好处在于,当涉及混合类型操作(比如比较有符号数和无符号数)的时候,我们不必担心上面描述的边界溢出情况(比如-1 会被提升为一个非常大的正数)。我的一个建议就是:避免在一个表达式中混合使用无符号数和有符号数。
如果你一定要用,最好在表达式中使用强制类型转换,使它们同时为有符号数或无符号数,精确地告诉编译器你想干什么,别让编译器隐式地替你拿主意。
目前,无符号数多用在位段以及位操作上。在位操作中,有的时候需要逻辑移位,而不是数学移位,这个时候我们就必须用无符号数了。关于什么是逻辑移位,我们在4.7 节中再详细讨论。
3.5 int 和char 的关系
3.5.1 char 就是short short
3.5.2 char 的符号
3.6 浮点数的有效位
3.7 判断两个浮点数相等
3.8 常量与常量后缀
3.9 sizeof 运算符
3.9.1 sizeof 返回值
3.9.2 sizeof 的用处
3.9.3 sizeof(指针)和sizeof(数组)的区别
3.10 本章小结
参考文献:1.《C语言点滴》
相关文章推荐
- 《C语言点滴》读书笔记(2)第2章编程基础知识(关键词:C语言/重构)
- [零基础学软件开发5]c语言数据类型初探
- [C语言 - 1] C语言数据类型
- c语言数据类型陷阱
- 在C语言中,double、long、unsigned、int、char类型数据所占字节数
- C语言(数据类型)
- Objective-C语言学习之 4000 数据类型
- C语言博客作业--数据类型
- 《C++捷径教程》读书笔记--Chapter 9--更多的数据类型与运算符(第二部分)
- C语言博客作业--数据类型
- C语言博客作业--数据类型
- C语言博客作业--数据类型
- c语言的数据类型 范围 解释
- C语言数据类型及内存占用
- 在C语言中,double、long、unsigned、int、char类型数据所占字节数
- C语言数据类型转换
- 犀牛书第3章 JavaScript数据类型和值(上篇)
- Poedu_C语言提升_Lesson01_20161108_数据类型
- C语言的数据类型(二)常量
- 读书笔记《谭浩强C++》2_数据类型与表达式