提示:先赞后看,养成习惯
我们都知道在C语言中,整型数据类型包括:char,short,int,long,
long long,并且我们知道他们在内存中分别占1,2,4,4,8个字节,既然开辟了一定的空间,我们就要物尽其用啊!那么我们就要把想存的数据放入在对应的空间中啊,既然存进去了,那它又是怎么存的的呢?接下来博主就和大家一起来讨论下。
提示:以下是本篇文章正文内容,下面案例可供参考
在讨论整型数据在内存中是如何存储的,我们就必须知道原码、反码、补码。这几个关键的概念
原码:*是一种计算机中对数字的二进制定点表示方法,原码表示法在数值前面增加了一位符号位(即最高位为符号位):正数该位为0,负数该位为1(0有两种表示:+0和-0),其余位表示数值的大小。**说人话就是比如说
十进制数:10 ---------------所对应的二进制数为0 1010,
那么我们就称“1010”为10的原码;
这个最高位的“0”表示的是正号,并没有实际意义也被称为符号位,加粗的为有效数据;
十进制数:13----------------所对应的二进制数为0 1101,
那么我们就称“1101”为13的原码;
同理这个最高位的“0”表示的是正号,并没有实际意义也被称为符号位,加粗的为有效数据。
十进制数:-17--------------所对应的二进制数为1 10001,
那么我们就称“110001”为-17的原码;
这个最高位的“1”表示的是负号,并没有实际意义也被称为符号位,加粗的为有效数据。
举了几个简单的栗子,现在对原码有了一定的了解了吧。
反码:
对于<正数>来说:反码就是原码;比如:13的
原码:01101,
反码:01101;
对于<负数>来说:符号位不变,其他位按位取反
比如说-17
原码:110001,
反码:101110
补码:
对于<正数>来说:
补码等于反码等于原码;比如13的
原码:01101
反码:01101
补码:01101
对于<负数>来说:
补码等于反码+1(符号位也会参与+1的运算)或者说等于原码->反码(符号位不变,其他位按位取反)->补码(反码+1);
比如-17:
原码:110001
反码:101110
补码:101111
其中呢,整型数据在内存中是以补码的形式进行存储的!!!!!
以上我们介绍了原码,反码,补码的概念,同时也说明了整型数据在内存中是以补码的形式存在的,那么我们现在来实验一下,看是不是真如我们叙述的一样。
int main()
{
int a = 10;
return 0;
}
分析:
1、a的类型为int,所以不管三七二十一先开4个字节再说
2、十进制数10
原码:0000 0000 0000 0000 0000 0000 0000 1010
反码:0000 0000 0000 0000 0000 0000 0000 1010
补码:0000 0000 0000 0000 0000 0000 0000 1010
3、把补码(以字节为单位)一个字节一个字节的放在所开的内存中,就得到了以下情况:
这里我们打开调试窗口,找到内存
我们从右往左看(为什么是从右网左看,下文会解释)是不是 00 00 00 0a,这是一串16进制数据,这是编译器为了方便程序员阅读,而特意显示的,但实际中所有数据在内存中都是以二进制的形式存在内存中的,现在我们把这一串16进制转换为二进制;得
00000000 00000000 00000000 00001010,是不是和我们前文的10的补码形似一模一样,这是不是就验证了我们前文的叙述;
如果你还有疑惑,那我们再来整个负数:-10
int main()
{
short a = -10;
return 0;
}
模仿前面的步骤:
1、a为short型,管他三七二十一,我咔咔咔先在内存中开辟2个字节的空间再说;
2、十进制数:-10;
原码:1000 0000 0000 1010
反码:1111 11111 1111 0101
补码:1111 11111 1111 0110
那么在a的空间中是不是就是放的如下序列:
这里我们打开调试窗口,找到内存,看一看内存中是不是放的这样:
与前面一样,我们把这个16进制转换一下,就得到了
11111111 11110110是不是与前文我们分析的一模一样;
到了这里,大家应该对整形数据的”存“有了一定的了解了吧!
读者可以下来自行练习一下 char a=-17;(在内存中的形式为:1110 1111)
1、先看这个数据是什么类型,确定它在内存中的大小,然后咔咔咔把他的空间给开好;
2、现在不需要管他是什么类型的,直接写出要存入的数据的补码,然后存进去就行了;
既然我们”存“了数据,那么我们需要使用的时候呢我们就必须把它给”取“出来,我们怎末取呢?在讨论
”取“数据之前呢,我们首先引入两个概念->大小端
表示数据在存储器中的存放顺序
小端模式:数据的高字节位存放在高地址处,低字节位放在低地址处。计算机从低地址开始读取数据,最先读到的就是数据的低字节位,也就是我们平常说的“低位”
大端模式:数据的高字节位存放在低地址处,低字节位放在高地址处。计算机从低地址开始读取数据,最先读到的就是数据的高字节位,也就是我们平常说的“高位”
什么又是高字节呢?
举个简单栗子:
short a=10;
补码: 0000 0000 0000 1010
前八个比特位为高字节位,等级依次往后递减;
画个图
像这样高字节放在低地址处,低字节放在高地址处的就是大端的存储模式;
而像这样的低字节放在低地址处,高字节放在高地址处的就是小端存储模式了;
这解释了,为什么前文我们再看int a=10的内存的时候是从右往左看了而不是从左往右看了,因为在vs的编译器下,它是按小端的存储模式进行存储的,从右往左地址是依次减小的,因此我们取数据的时候是从右往左取的;
int main()
{
int num = 0x1234;
char* p = (char*)#
char a = *p;
if (a == 0)
printf("大端\n");
else
printf("小端\n");
return 0;
}
分析:0x1234
原码:0000 0000 0000 0000 0001 0010 0011 0100
反码:0000 0000 0000 0000 0001 0010 0011 0100
补码:0000 0000 0000 0000 0001 0010 0011 0100
那么它的补码存放形式无非就这两种,那么我们就取其首地址的补码,把它放在一个char类型的空间里,我们分析到首字节里面放的要么是0,要么是52;于是我们就拿char里面的值与0作比较;如果等于0,则就是大端存储模式;如果不等于0,就是小端存储模式;
目前来说大多数机子都是小端;
这主要是由硬件厂商来决定的;
或者说再举个简单:栗子剥鸡蛋,你只能从顶部和尾部剥
有的人喜欢从顶部开始剥,有的人喜欢从底部开始剥,并且都有自己的理由,谁也不能说服谁。但是不管怎么剥,最终都能得到一个完整的蛋,于是这两种剥蛋的方式就一起存在了下来,大小端的问题也类似于此;
有了前面的知识做铺垫,接下来取数据变得格外的简单
举个栗子来说明
signed short val ;判断val为多少?
------>地址从左往右依次增大
val在内存中存放的数据:0000 0000 0000 1010
接下来”取“数据
1、判断机器是大端还是小端;
如果是大端,则实际val的补码序列为:0000 0000 0000 1010;
如果是小端,则实际val的补码序列为:0000 1010 0000 0000;
2、根据数据类型判断最高位是否为符号位,若是则根据原码,反码,补码的规则抓换为原码在输出;若不是,则直接根据此二进制序列直接输出十进制;
此题来说,val是signed short的类型,故最高位位符号位,符号位为0,故此数为正数,故val所对应的十进制数为10(大端)或2560(小端);
读者可以自行验证
unsigned short num;在内存中序列为:0000 0000 0001 0011
num的值(在大端和小端机器下)
对应十进制:19(大端)或4864(小端)
用char类型来说明吧!
除去符号位
0 1111111---------(127)
…
0 0000000---------(0)
1 0000000---------(0)
…
1 1111111---------(-127)
我们会发现
0的表示似乎重复了,对于计算机来说他是不会允许这样的情况出现的,为了物尽其用,必须用以一个来表示0,那当然是00000000了,那这个10000000怎么办呢-127~127都被占用了,只有可能是-128或者128了,为了方便起见,我们规定10000000表示-128;于是char的范围为-128–128;后面的有符号整型亦是如此;
对于这个-128来说有一点特殊,于是我们来讨论一下它;
首先编译器运行的结果是-128;
接下来我们“手算一下”看看是否与编译器一致;
1、char类型,先开1个字节;
2、-128
原码:1 1000 0000
反码:1 0111 1111
补码:1 1000 0000
由于char占8个比特位,故在往char里面存的时候会发生截断,既在char中实际放的是10000000;接下来我们根据”取“的方式还回去,
发现还不回去,于是我们便规定在有符号char中如果补码为10000000则,不用在转换,直接输出-128;后面的有符号整型也是如此;