目录
原码,反码和补码
在计算机中数据是以二进制进行存储的,并以其二进制的补码进行存储
二进制分为原码,反码,补码,且二进制的最高位称为符号位
原码就是将这个数字转化成二进制的形式
对于正数和零,其原码,反码,补码相同,而对于负数,其原码中符号位不变,其他位按位取反得到反码,反码加1得到补码
以10 和 -10 为例
原码:
10 00000000000000000000000000001010
-10 10000000000000000000000000001010
反码:
10 00000000000000000000000000001010
-10 111111111111111111111111111111110101
补码:
10 00000000000000000000000000001010
-10 111111111111111111111111111111110110
在计算机中之所以使用补码对数据进行存储,主要是因为 补码中 0的表示 是唯一的:
原码 :10000000000000000000000000000000
00000000000000000000000000000000
表示一个是 -0 一个是 0,而 -0 和 0 都是零,则导致 0 的表示方法不唯一,在计算中其表示的数据范围将减少
并且cpu中只有加法器没有减法器,使用补码可以简化硬件,例如
1 - 1 可以看成 1 + (-1)
利用原码:
00000000000000000000000000000001
10000000000000000000000000000001
结果:
10000000000000000000000000000010
而10000000000000000000000000000010 表示的是-2,与我们想要的结果不符
利用反码:
00000000000000000000000000000001
111111111111111111111111111111111110
结果:
1111111111111111111111111111111111111
将其符号位不变并将其他位按位取反得到原码
10000000000000000000000000000000 表示的是-0,虽然得到了0,但因为第一个原则,所以不适用
利用补码:
00000000000000000000000000000001
1111111111111111111111111111111111111
结果
100000000000000000000000000000000
因为在计算机中只能存储32位bit的数据,而其是33位所以会截断,其在计算机中的结果变为
00000000000000000000000000000000,也就是0,说明其可以得到零,且零的表示是唯一的,故而采用补码来进行数据的存储
大小端字节序存储
在内存中,数据的存储又分为 大端字节序存储 和 小端字节序存储,其方式是与硬件结构相关,而与编译器无关,其中大端字节序存储就是将高的二进制序列存到高位,小的二进制位存到低位,而小端字节序存储与其恰恰相反。
比如我们定义一个整型:
int a = 0x11223344;
其是一个用十六进制写出的数字最高位为 11 其次为 22 ,33 最低位 为44
若其在内存中的存储形式为 11 22 33 44 则说明是大端字节序存储
若其在内存中的存储形式为 44 33 22 11 则说明是小端字节序存储
那么我们能否编写一段代码,来检测自己的设备到底是大端字节序存储还是小端字节序存储呢?
答案当然是肯定的
比如以下代码:
#include <stdio.h>
int main()
{
int a = 0x11223344;
int b = *(char*)&a;
if(b == 0x44)
{
printf("小端字节序存储\n");
}
else
{
printf("大端字节序存储\n");
}
return 0;
}
我们对a取地址可以得到a的地址,若此时解引用则会的到a原来的值,因为int类型的指针在解引用时会将其接下来的四个字节当成一个数据,所以此处我们利用char*进行强制类型转换,而char类型的指针,在解引用时只会将当前的这一个字节当成一个数据进行处理,这样我们就得到了a在内存中第一个字节所存储的元素,若我的设备是小端字节序存储,那么我获取到的值就是0x44,否则就应该是0x11,所以可以利用数据的比较来获取我们当前设备的存储形式。
为了便于使用,我们可以将其封装成一个函数:
#include<stdio.h>
//1,表示小端字节序存储, 0表示大端字节序存储
int check_sys()
{
int a = 1;
return *(char*)&a;
}
若其为小端字节序存储,则应当取到1,否则应当取到0,直接将其取到的数据返回即可
有符号和无符号
在C语言中,基础数据类型可大致分为整型和浮点型(实型)
整型家族:
char unsigned char signed char
short [int] unsigned short [int] signed short [int]
int unsigned int signed int
long [int] unsigned long [int] signed long [int]
long long [int] unsigned long long [int] signed long long [int]
因为在计算机中,字符型存储的是字符的ASCII码值,所以也可以分为整型
其中又分为有符号和无符号两类,有符号则最高位是符号位。
用unsigned 标识出来的是无符号类型,用signed 标识出来的是有符号类型,在不写其是否有符号时,默认时有符号的,对于无符号数,其有对应的格式控制字符
%u ---- 无符号整型
%lu ---- 无符号长整型
%llu ---- 无符号长长整型
其中无论是有符号数,还是无符号数,在内存中的存储方式都是相同的,只是解析的时候有所差异,对于有符号数,最高位被解释位符号位,而对于无符号数,最高位被解释位数字。
测试案例
eg.1
#include<stdio.h>
int main()
{
char a = -1;
signed char b = -1;
unsigned char c = -1;
printf("a=%d,b=%d,c=%d",a,b,c);
return 0;
}
其中a是没有注明是否有符号的,则默认是有符号的,所以a在内存里的二进制序列为11111111,
b注明是有符号的,所以b在内存里的二进制序列为11111111
c注明是无符号的,并且-1的2进制序列(补码)为11111111,
而其输出控制字符为均为%d,而他们都是char类型,所以要进行整型提升
提升时,就会按照其符号位进行提升,即为最高位,对于无符号数据类型,则以0进行提升
则 a : 11111111111111111111111111111111
b: 11111111111111111111111111111111
c: 00000000000000000000000011111111
所以a为-1,b为-1,c为2^8-1也就是255
运行结果:
证明我们的推理是正确的
eg.2
#include<stdio.h>
int main()
{
char a = -128;
printf("%u\n",a);
return 0;
}
对于此题,a未注明是否有符号,所以当有符号处理,-128的补码为10000000
所以a中存储的就应当是10000000,但需要注意的是格式控制字符 %u,根据上面的介绍,我们已经得知,%u 是无符号整型的,所以需要对a进行整型提升,将a变为
11111111111111111111111110000000
也就是2^32 - 2 ^ 7 ,借助计算器我们可以得到其结果位4294967168
运行结果:
eg.3
#include<stdio.h>
int main()
{
char a = 128;
printf("%u\n",a);
return 0;
}
此题与上一题相同,也需要对a进行整型提升,而a位字符型,只能存储1个字节,其范围为
-128~127,此时存的数据为128,而128对应的二进制序列为00000000000000000000000010000000,存储时整型截断,所以a在内存中的二进制序列为10000000,也就是 -127,所以此题与上一道题的答案相同,也就是4294967168