C语言给定了一些基本的数据类型
char 类型
int 类型
long 类型
float 类型
double 类型
前三者都属于整型家族,后两者属于浮点型家族,为什么char属于整型家族呢?这是因为字符是以对应的ASCII码值存放到内存中,本质上是属于整数的,所以将其归类为整型。
那么我们在创建这些变量且赋予它们具体数值的时候,它们又是如何将这些数值数据存放到内存中的呢?我们都知道任何形式的数据(字符,图片, 音频......)放到电脑中都是以二进制的形式存放的
例如 我们创建一个整型变量,并赋予它一个数值
int a = 10;
a中的数据放在内存中需要转化为二进制形式
将10转化为二进制形式为 1010 因为int有32个比特位的大小,所以最终的形式如下
00000000000000000000000000001010
这样我们就解决了存放整数的问题,但很快会发现,如果存放一个负整数该怎么存呢?该怎么表示这个数字是负的呢?聪明的科学家想到了这样一个办法,那就是将这个二进制序列的最高位定义为符号位,如果最高位上的数字为0,那表示这个数字是正数,如果最高位上的数字为1,那表示这个数字是负数,例如下面的表示
int a = 10;
a中的数据放在内存中转化为二进制形式为
00000000000000000000000000001010
int a = -10;
a中的数据放在内存中转化为二进制形式为
10000000000000000000000000001010
好了,现在正整数和负整数在内存中存放的形式都解决了,哈哈,不过问题又来了,正整数和负整数在进行加减运算的时候就出现问题了,如下
int a = 10;
int b = -1;
a 在内存中的二进制形式为 00000000000000000000000000001010
假设 b 在内存中的二进制形式为 10000000000000000000000000000001
如果将a 与 b 相加
a 00000000000000000000000000001010
+
b 10000000000000000000000000000001
= 10000000000000000000000000001011
a和b相加,结果却变成了 -11,怎们办,这里不得不佩服科学家的聪明,引入了原码 补码 反码的概念,那么这些具体有什么作用呢?接下来分析一下。
正数的原码 反码 补码相同
int a = 10;
a 的二进制形式为
00000000000000000000000000001010 //原码
00000000000000000000000000001010 //反码
00000000000000000000000000001010 //补码
负数的反码为 原码的符号位不变,其他位取反。负数的补码为反码加1
int b = -10;
b 的二进制形式为
10000000000000000000000000001010 //原码
11111111111111111111111111110101 //反码
11111111111111111111111111110110 //补码
这里需要知道的是,无论正数还是负数,都是以补码的二进制序列放到内存中的
因此实际上,a放到内存中是补码 00000000000000000000000000001010
b放到内存中是补码 11111111111111111111111111110110,接下来我们通过编译器去验证一下。
验证结果是没有问题滴,说明确实是这样存储的,但至此,可能对其作用仍不了解,那接下来说一下反码 补码的具体的作用,还是以加法为例
int a = 10;
a 的二进制形式为
00000000000000000000000000001010 //原码
00000000000000000000000000001010 //反码
00000000000000000000000000001010 //补码
int b = -5;
b 的二进制形式为
10000000000000000000000000000101 //原码
11111111111111111111111111111010 //反码
11111111111111111111111111111011 //补码
因为都是以补码存放到内存中,所以在内存中
a 为00000000000000000000000000001010
b 为11111111111111111111111111111011
a 00000000000000000000000000001010
+
b 11111111111111111111111111111011
= 00000000000000000000000000000101
转化为十进制就是5 是不是很奇妙,这就是反码 补码的妙用,非常漂亮的的解决掉正负数进行加减运算的问题,其他的乘除运算都可以转化为加法运算,这样也就同时解决掉其他的运算问题。
这里补充一点,补码取反加1是可以转化为原码的
我们试一试
11111111111111111111111111111011 这是b的补码,现在我们符号位不动,其他位取反
10000000000000000000000000000100 这是补码取反后的结果,接下来我们把它加1
10000000000000000000000000000101 这是加1后的结果,发生什么事了,是不是又变回了原码
接下来,举点栗子,加深对上面知识的理解
char a = -1; //注:char 是 signed char还是 unsigned char取决于编译器
signed char b = -1; //不过一般都是signed char
unsigned char c = -1;
printf("%d %d %u", a, b, c);
那么打印的结果分别是什么呢?
一起分析一下吧
以上就是整型数字在内存中的存储方式,但仔细观察下图,会发现一个奇怪的现象
就拿int a 来说 我们分析的存储方式应该是00 00 00 0a ,而编译器上却显示存储形式为0a 00 00 00
这其实就牵涉到大端,小端存储的问题,为什么会这样呢?因为int 类型接连占用四个字节,该空间的数据的存放位置就得有一个规定,我们同样可以将a中得数据以00 0a 00 00或者是00 00 0a 00的方式存储,但这些都太过麻烦了,00 00 00 0a 和 0a 00 00 00这两种存储方式最符合我们的思维习惯,就沿用至今,分别叫大端存储法和小端存储法。大端还是小端是取决于硬件的,与编译器无关,一般个人pc用的是小端存储法。
接下来我们一起分析一下大端与小端,然后再写个小程序来判断自己的设备采用的是大端还是小端
那我们怎么判断自己的设备是大端还是小端呢? 同样是 int a = 10; 我们想看看它是大端存放还是小端存放,只需查看该空间的第一个字节,因为空间的第一个字节是处于低地址的,如果该空间的第一个字节没有存放 0a 则说明是大端存放,如果存放了0a,则说明是小端存放。
代码实现如下
int main()
{
int a = 10;
char *p = (char*)&a;
if (10 == *p)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}