目录:
一.进制转换
1.1那么进制数之间的转换关系呢?
二.有符号数与无符号数
2.1数据类型的数据表示范围
三.数据在计算机内的存储形式
3.1原码
3.2 反码
3.3 补码
四.既然数据以补码形式存储在计算机内,那么我赋值的时候是给的补码还是原码?
五.如果数值赋值过程中超过了自身数据类型范围会怎么样?
一.进制转换
所谓R进制数就是逢R进1
十进制数: 逢10进1
十六进制数:逢16进1
八进制数:逢8进1
二进制数:逢2进1
我们日常生活中描述数据的大小通常以十进制的自然数进行描述,但是计算机内部只能存储二进制形式的数据。并且计算机存储的单位为一个字节,也就是八个二进制位。如果用二进制描述一个数值很大的十进制数通常需要很多位二进制数来表示,如十进制数65535要用二进制数1111 1111 1111 1111两个字节来描述,很多时候这样冗长的二进制数据很难直观的观察数值的大小。
我们知道:
八进制数的数据范围为0~7,最大的数为7,二进制表示为111,则一个八进制数可以描述三位二进制数。
十六进制的数据范围为0~F,为表示10~15之间的数据,分别依次用A~F来与之对应。因为最大的数为15(或说为F),二进制表示为1111,则一个十六进制数可以描述四位二进制数。
那么,一个比较大数值的数据用八进制或者十六进制表示就可以即简洁又直观的了解数据在计算机内部的存储形式。
我们也会发现,在C语言中的变量通常是也只能是用十进制数、八进制数、十六进制数这三种进制方式赋值的。为方便区分我们通常在八进制数前以数字0做为前缀,在十六进制数前以“0x”或"0X"作为前缀。
1.1那么进制数之间的转换关系呢?
这里我有一些总结:
R进制 -> 十进制:
以R为权转换。
如:125 = ...... + four * R^(4) + three* R^(3) + two *R^(2) + one * R^(1) + zero * R^(0)
若R为八,zero = 5 one=7 two =1 即125=0157
若R为十六,同理125=0x7D
若R为二,同理125=0111 1101
十进制->R进制:
整数部分:÷R取余,直到商为0截止,逆序写
小数部分:×R取整,顺序写
二进制<->十六进制:
以4位二进制数等效1个十六进制数
二进制<->八进制:
以3位二进制数等效1个八进制数
十六进制<->八进制数:
以二进制数作为桥梁,再等效。
如果有需要详解这个总结的在留言区cue下。
二.有符号数与无符号数
前面我们提到了数据在计算机内部以二进制数01存储,因此数据变量能存储的范围与其在内存中所能占据的字节大小(一个字节8位)有关。
sizeof(char) =1 ——> 以8位二进制数在计算机内部存储 ——> 0000 0000 ~ 1111 1111 |
sizeof(short)=2 ——>以16位二进制数在计算机内部存储 ——>0000 0000 0000 0000 ~ 1111 1111 1111 1111 |
我们还提到了前面的进制转换,通过二进制与十进制转换,我们可以知道char类型数据的数据范围在0~255,short类型的数据范围在0~2^(16)-1之间。可是,我们有时需要用负数来描述一个数据的大小,光正数可不行。可是计算机内部是以二进制数形式存储数据的,那要怎么体现正负呢?伟大的科学家定义了一种方式,就是将最高位数作为符号位,剩余的其他位为数据位。这时就有了有符号数和无符号数的概念。
简单来说:
无符号数就是只有非负的数,即大于等于零的数。二进制数中只有数据位,用来表示数值的大小。
有符号数就是既有正数又有负数的数。二进制数中将最高位作为符号位,剩余位作为数据位。
2.1数据类型的数据表示范围
前面我们提到有符号数是通过将最高位作为符号位,其他位作为数据位来进行表示的数。
以char型为例,0 000 0000 ~ 0 111 1111, 范围在+0~+2^(7)-1
1 000 0000 ~ 1 111 1111,范围在-0~-2^(7)-1
由于+0=-0,我们规定1 000 0000表示为-2^(7),因此char有符号数范围在-2^(7)~2^(7)-1
通过推理,我们可以总结为:
某一数据类型在内存存储中占的位数为n
考虑无符号数时,由于每一位都是数据位:无符号数的表示范围在 0~2^(n)-1
考虑有符号数时,由于最高位为符号位:有符号数的表示范围在 -2^(n-1)~ 2^(n-1)-1
那么我们可以知道,int类型数据类型为4字节,占32位,其无符号范围在0~2^(32)-1,有符号范围在-2^(31)~2^(31)-1,其他类型同理可推
三.数据在计算机内的存储形式
3.1原码
我们已经知道数据在计算机内部是以二进制形式存储的了。我们将数最原始的二进制形式称为原码(如4的原码为 0000 0100)。
由于需要考虑到正负数的表示,我们会发现由于最高位为符号位,负数的符号位为1,正数的符号位为0,那么如果两个负数相加,由于两个符号位为1相加满足逢2进1,导致符号位溢出,最后符号位表示结果为0,(即符号位1+符号位1 = 0),那么这样的话不就表示负数+负数=正数,同理由于(符号位0+符号位1=1),表示正数+负数=负数,结果也是错误的。
也就是说,负数的原码参与的计算结果是错误的。此外,还有0的表示问题,在原码中,0存在了两种表示方式,即0000 0000 (+0) 和 1000 0000(-0)。逻辑推理起来是这样的情况,我们还是随便找两个数的原码计算以下:
1 : 0000 0001 -1: 1000 0001 1+(-1):1000 0010 ——>-2 这样计算下来相加的结果居然是-2,而不是0 |
总结下:
原码存在的问题是:
1>有负数的原码参与的运算结果是不正确的。
2>无法解决0存在两种状态的问题
3.2 反码
如何解决负数的原码参与的运算结果错误问题呢?科学家通过(-n)+(n)=0这个角度想到了一个解决办法,由于二进制数逢2进1,我们可以保留负数原码中最高符号位不变,剩余的数据位取反。这样所有的(-n)+(n)二进制结果都会= 1111 1111=-0 =0
我们继续以 1+(-1)的例子:
1 : 0000 0001 -1: 1111 1110 1+(-1):1111 1111 ——>-0 |
但是,这样的做法虽然可以解决(-n)+(n)=0的问题,那是否可以解决所有的负数参与运算结果问题呢?事实上由于我们还是将1111 1111视为0,同时0000 0000还是为0,即存在两种状态的0的原因,我们随便拿两个不是互为相反数的数字相加都能发现计算结果会比实际结果小1
我们这里拿8+(-1)进行尝试:
8: 0000 1000 -1: 1111 1110 8+(-1):0000 0110 ——>6 实际上8+(-1)=7,大了结果1. |
我们再总结下:
对负数来说,反码=原码的符号位不变+其他各数据位取反
反码存在的问题:
1>不能完全解决负数参与的运算结果问题,除了相反数向加,计算结果都比实际结果小1
2>还是没有解决0的两种状态问题 :1111 1111 0000 0000
3.3 补码
通过反码存在的问题,科学家发现了玄机,在反码的基础只需要补个1就可以完美解决两个同时存在的问题!
这里我们需要注意,我们自始至终都是针对负数的原码参与的计算结果错误来解决的,所以我们这里提到的反码和后面提到的补码都是针对负数而言。也就是说,只有负数的反码和补码才会在原码的基础上发生变化。
我们最后总结下:
补码=反码+1 =原码的符号位不变 +其他各数据位取反 + 1
补码就是计算机内部存储的数据形式!
它可以同时解决0的状态问题(只有0000 0000这种状态了)和负数参与的运算结果
四.既然数据以补码形式存储在计算机内,那么我赋值的时候是给的补码还是原码?
我们已经知道,每个数据在计算机内部是以补码的形式存储的,为了直观观察数据,我们往往需要十进制输出监视某个数据。但是,其实十进制的表示往往离不开它自身的原码。
那在计算机存储这个数的补码怎么得到其原码表示呢?
数据的输入与输出有两个过程:
数据输入:对于数据输入到存储空间中,原码转换为补码
数据输出:对于从数据对应的存储空间转换为十进制原始数据,补码转换为原码
这两个过程数据的变换过程是一模一样的,也就是取补码的过程
都是两步走,第一步保留符号位,其他各位取反;第二步补1
其实,根据补码的补码=原码就保证了数据表示的不变性。
其实以上就是说,十进制数存入的时候其实是将其转换为补码形式存储在计算机内存当中。当需要取出这个数据的时候,基于补码的补码等于原码的原理,将计算机的存储形式再取补得到原数据。
那么问题是,如果不是十进制数呢?我直接以十六进制或八进制数方式赋值还需要再取补存入计算机,在计算机内存储空间取出来再取补得到原数据吗?显然是不必要的,因为八进制数和十六进制数其实就是二进制的序列,是可以直接和内存对齐的。也就是说,在赋值的时候,赋值的十六进制数本身就作为补码存入计算机内部了。不需要再取补后存入计算机。
我们可以验证一下:
int main()
{
char a = 0xEE;//1110 1110
printf("%d\n", a); //输出结果为-18(1001 0010)
system("pause");
return;
}
总结下:
计算机数据的存储的原则一定是保持同值不变
如果赋值时(输入):
赋的是十进制数,给的是原码;
如果赋的是八进制数或者十六进制数,给的是补码;
如果打印时(输出):
十进制打印要的是原码;
如果是十六进制或八进制打印要的是补码;
五.如果数值赋值过程中超过了自身数据类型范围会怎么样?
好了回归这个文章的重点。我们已经了解到:
1.不同的数据类型占据的内存空间不同,取值范围也有差异。
2.有符号和无符号的差别以及数值范围,
3.输入十进制数、十六进制、八进制数后内存的存储形式
如果赋值和输出时数据都保持在范围内,数据当然不会有什么变化,根据原数据值不变规则那个值还是那个值,这当然没有什么讨论的意义。那么,如果存储的数据是超范围的不常规的数值呢?输出结果会是什么样的?会造成什么样的结果?以下这个例子就可以说明了:
/* 我们先来看看有符号输出和无符号输出的结果差异 */
short a = 0xfffe;
printf("%d\n", a); //结果为: -2
//有符号输出,最高位为1,负数,取补后原码为:1000 0000 0000 0010
unsigned short a = 0xfffe;
printf("%u\n", a); //结果为: 65,534
//无符号输出,不存在负数,原码=补码=FFFE=65534
/* 接下来我们来看看超范围数据结果会是什么? */
short a = 0x7fff + 2;
printf("%d\n", a); //结果为: -32767
//相当于存入补码:1000 0000 0000 0001
//有符号输出,符号位为1,负数,取补后原码:1111 1111 1111 11111
unsigned short a = 0xffff + 5;
printf("%u\n", a); //结果为多少? 4
//相当于存入补码0000 0000 0000 0000 0100
//无符号输出,不存在负数,原码=补码=0000 0000 0000 0000 0100
结束语:
文章整理不容易,麻烦尊重知识尊重劳动,请勿抄袭剽窃。
如果本文章对你有帮助的话希望多多支持嘿