分类: C/C++
2016-12-11 15:04:00
(1)原码,拿char型变量来说,-8和8的源码对应的二进制有所不同。对于正数最高位为0,负数最高位为1。
[+8] = [00001000]原
[-8] = [10001000]原
(2)反码,反码是原码对应的二进制各位取反得到的数。如果原码为正数(不管该变量是有符号型还是无符号型),则反码等于原码;若原码为负数,则反码为原码除符号位以外的位取反。
[+8] = [00001000]原 = [00001000]反
[-8] = [10001000]原 = [11110111]反
(3)补码,对于正数,补码与原码相同;对于负数补码是反码加1的结果。
[+8] = [00001000]原 = [00001000]反 = [00001000]补
[-8] = [10001000]原 = [11110111]反 = [11111000]补
关于无符号型变量的取值范围大家都很清楚,内存中是按原码存储的,符号型变量的正数也是按原码存储。而符号型变量的取值范围负数要比正数多一个,负数是按补码存储的。比如有char型变量a = -8,那么通过16进制打印出来是0x88,还是0xf8呢?对于初学者的你我一定会认为打印的是0x88,但结果却是0xf8,其实答案就是前边提到的-8的补码。
[+8] = [00001000]原 = [00001000]反 = [00001000]补 = [00001000]存储
[-8] = [10001000]原 = [11110111]反 = [11111000]补 = [11111000]存储
那么加减法是如何实现的呢?其实就是补码直接相加之后得到新的补码,如果是有符号型,且符号位为1,则该补码-1,再将除符号位以外的位取反即得到原码;其他情况则补码和原码相同。
[+8] + [-8]
= [00001000]补 + [11111000]补
= [00000000]补
= [00000000]存储
= [00000000]原
= [0]
[+1] + [-8]
= [00000001]补 + [11111000]补
= [11111001]补
= [11111001]存储
= [10000111]原
=[-7]
[+8] + [-1]
= [00001000]补 + [11111111]补
= [00000111]补
= [00000111]存储
= [00000111]原
=[7]
问题又来了在符号变量中既然[00000000] 原表示0,那么[10000000] 原表示-0吗?怎么符号型变量有两种表示0的方式,这显然是一个问题。下边我们来计算一下-0补码,我们补码取9位。
[10000000] 原 = [11111111]反 = [100000000]补
可以看出,当截取补码低8位后,[10000000] 原等于[00000000] 补,说明-0确实等于0。这样一来char型的取值范围就变成了-127到127,[10000000] 原不使用岂不是浪费。下边我们来看看-128的补码究竟是多少。
由于-128要有符号位,所以只能用9位表示原码。
[110000000] 原 = [101111111]反 = [110000000]补
可以看出-0的补码和-128的补码并不相同,但是当只保留低8位时,确实是相同的。由于-128的补码只保留后8位并不会影响计算,所以使用了[10000000] 原来表示-128,即char型变量的取值范围变成了-128到127,其它short型,int型道理相同。为了说明[10000000] 原表示-128不影响计算,下边来举个例子。
[+127] + [-128]
= [01111111]补 + [10000000]补
= [11111111]补
= [11111111]存储
= [10000001]原
=[-1]
可以看出得到的结果确实是正确的。下边用c语言验证一下前边所说的一些结论:
结果:gcc version 4.8.1 (GCC)
从结果来看,我们可以发现赋值时,存储到内存的数据与其类型是否有符号没有关系。拿char型的-8和unsigned char型的-8来说,其存储到内存的数据都是[-8] = [10001000]原 = [11111000]补,而最后打印出来的10进制之所以不一样是因为printf根据符号关系输出的。
以前一直没有注意过这个问题,因为一直拿int型的变量来移位,基本上只用了前面的几位,自然没有考虑到符号位的情况。有符号和无符号的变量的区别就在于,当向右移位时,符号变量最高位会补1,而无符号变量最高位会补1,可以从下边这个示例得到验证。
从结果中可以看出符号型右移时高位确实补了1,而左移时低位任然补0.