一、引出问题
在学习C语言单目操作符中~
按位取反的过程中,对这样一段代码的结果产生了疑惑:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>int main() {int a = 0;int b = ~a;·//按位取反printf("%d\n", b);return 0;
}输出结果:
-1
首先,int整型占4个字节,0化为二进制形式为00000000 00000000 00000000 00000000这样,按位取反后是11111111 11111111 11111111 11111111,化为十进制不应该是一个很大的负数吗,而输出结果却为-1。
之后我查阅资料才发现,我忘记了负数在计算机内存中是以补码的方式存储和进行运算的。于是我又将原码、反码、补码相关的知识复习了一遍,温故而知新,重新总结如下。
二、深入理解原码、反码、补码
在计算机中,数字的二进制位的第一位是符号位,0为正,1为负
首先要明白,原码、反码、补码是计算机用于表示带符号整数的三种编码方式
- 原码:原码最高位为符号位,其余位为数值绝对值的二进制值,如5的原码是0000 0101,-5的原码是1000 0110。原码表示最直观,但在进行加减法运算时存在问题,如5+(-5)理论上等于0,但原码相加得1000 1011并不是0000 0000
- 反码:正数的反码与原码相同,负数的反码是将原码符号位不变,其余位按位取反。如5的反码即原码0000 0101,-5的反码为1111 1010。
引入反码是为了更好的解决二进制正数与负数间的加减法问题,如5+(-6)即0000 0101 + 1111 1001 = 1111 1110(反码),转为原码即符号位不变,其余位取反,得1000 0001,正好是-1。
但反码仍然存在溢出和零的表示问题,如-5+6,即1111 1010 + 0000 0110 => 溢出得0;-5+5即1111 1010 + 0000 0101 = 1111 1111(反码),转为原码即1000 0000,这就与0000 0000的0的原码表示方式存在冲突,一个数总不能有两种表示方法吧。于是就有了下面的补码。 - 补码:正数的补码与原码相同,负数的补码是将负数的原码符号位不变,其余位取反后再加1(即负数的反码+1)。如5的补码即原码0000 0101,-5的补码为1111 1011。
补码的引用很好的解决了加减法和0的表示问题,还能够自然地处理溢出,下面验证一下:- -6+5,即1111 1010 + 0000 0101 = 1111 1111,转为原码(补码-1,符号位不变,其余位取反)得1000 0001,即-1,没毛病。
- -5+6,即1111 1011 + 0000 0110 => 溢出且得0000 0001,正数,即原码本身,得1,没毛病。
- -5+5,即1111 1011 + 0000 0101 => 溢出且得0000 0000,与0的原码表示相同,没毛病。
由此可见,在计算机中,补码是最常见和最有效的带符号整数表示方式。
因此,在计算机中,带符号的整数在内存中存储的是其二进制的补码。
那么这就可以理解结果为什么为-1而不是一个很大很大的负数,以上代码的计算过程如下:
//a=0的补码(也是原码)
00000000 00000000 00000000 00000000
//~取反运算得到b的补码
11111111 11111111 11111111 11111111
//补码-1
11111111 11111111 11111111 11111110
//符号位不变,按位取反得到原码
10000000 00000000 00000000 00000001
即得到打印出来的结果为-1
终于,破案了~
三、关于原码、反码、补码之间的转换
1.对于正数,原码 = 反码 = 补码
2.对于负数,此图足以: