数据的类型

引例:

有如下代码

int i = -20;
unsigned int j = 10;
printf("%d\n", i + j);

这段代码的结果为-10.现在从编译器的角度来分析这段代码的结果。

上述代码进行计算,i的值能存进int里面,j的值也能存进unsigned里面,所以计算结果就是-10,那么在编译器看来,数据在内存中的存储是以补码的形式进行存储,所以首先要将十进制的数据转化为二进制的补码形式进行计算。

(负数的反码相对于原码符号位不变,其他位取反补码在反码的基础上+1

-20   10000000 00000000 00000000 00010100 原码

        11111111 11111111 11111111 11101011        反码

        11111111 11111111 11111111 11101100        补码

(正数的原码反码补码都相同

10    00000000 00000000 00000000 00001010 原码

        00000000 00000000 00000000 00001010 反码

        00000000 00000000 00000000 00001010 补码

所以i+j的结果是 11111111111111111111111111110110

这个结果表示的是补码相加的结果,要得到最终的值,还需要将补码转化为原码进行输出。

所以

方法一

       10000000000000000000000000001001 符号位不变,其他位按位取反

       10000000000000000000000000001010 加1 得到它的最终结果

方法二

        11111111111111111111111111110101  将补码进行-1操作

        100000000000000000000000000001010 再取反,得到最终的结果

可以发现,上述的两种计算结果一样。

但是这边我们看不到的是,编译器在处理的时候还将数据进行了强制的转化,这边是将

i+j的结果强行转化为unsigned型,但是又因为是以%d的形式去打印它,所以在这边结果显示是-10.

所以i+j的结果就是-10

那么通过上述的引例我们知道了数据在编译器中的计算原理,现在就正式介绍一下不同类型数据在内存中的结果。

字符在内存中的存储

现有如下代码:

unsigned int i = 0;
for (i = 9; i >= 0; i--)
{
	printf("%u\n", i);
}

结果是

 我们发现结果是个死循环,在从VS给的警告信息我们可以看到为何会出错

分析:

这边的打印结果是%u(无符号整形),况且i是unsigned型,-1的补码是11111111111111111111111111111111,但这边为unsigned,站在这个角度,它没有符号位,即11111111111111111111111111111111转化为十进制被打印(第一个1不再被当成符号位),BIN表示二进制,DEC表示十进制的数,OCT表示八进制,计算结果为

所以它的结果一直进行到3,2,1,0,-1,然后继续进行死循环。

若将%u换成%d那么这边是在进行判断条件时,继续将-1当成1通过判断条件,然后打印时继续以%d打印,所以打印-1等负数,然后程序继续进行死循环。

这边就是介绍两种情况下的结果,因为不常用,所以一般程序运行的结果也难以预料。

char类型数据的存储

eg;

在这串代码中,strlen(a)会打印数组a中,\0之前的一个字符,所以按照我们的正常预想,这个数组中他是-1,-2,...,-999,-1000,会打印-1000,但是结果却是255

 那是为什么呢

a数组是char类型

若是int型 那么 a数组的内容为-1 -2 -3 -4 -5 -6 -7 -8 -9……-126 -127 -128 -129…..-999 -1000

因为是char 所以是 -1 -2 -3 -4 -5 -6 -7 -8 -9 ……-125 -126 -127 -128  127 126 ……0 -1 -2 -3 ……-125……但是strlen找‘ \0’之前的字符,而\0的ASCLL值是0,char型的0,就表示\0,所以计算到0就会停止。即,它的输出结果就表示是128+127=255个字符长度,结果就是255,到0这个值后程序就会停止输入。将上述char在编译器中的数据情况画图如图所示:

浮点数在内存中的存储

常见浮点数

3.14159 、 1E10(1*10 的10次方)等

浮点数家族包括: float、double、long double类型。
浮点数表示的范围:float.h中定义

整型家族的类型的取值范围:limits.h

浮点型家族类型的取值范围:float.h

(上述.h文件可在电脑文件夹中找到)

eg:

#include<stdio.h>
int main()
{
	int n = 9;
	float* pFloat = (float*)&n;
	printf("n的值为: %d\n", n);
	printf("*pFloat的值为: %f\n", *pFloat);

	*pFloat = 9.0;
	printf("num的值为: %d\n", n);
	printf("*pFloat的值为: %f\n", *pFloat);
	return 0;
}

上述代码的结果为:

这串代码中第1、4的类型与输出结果匹配,所以打印正确。而第2、3的类型于输出结果匹配不一致,所以打印的结果就会出错,这就说明整型和浮点型在内存中的输入和读取形式是不一样的

那这又是怎么回事呢?

根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式

1.任何一个数,都能写成V=(-1)^S*M*2^E这种形式

2.(-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数

3.M表示有效数字,大于等于1,小于2

4.2^E表示指数位

 这是什么意思呢?举例说明:

以float类型的5.5为例

V=5.5(十进制的5.5)

  =1.011*2^2

  =(-1)^0*1.011 *2^2

那么5.5写成二进制的数时,整数部分是101,小数部分的0.5转化为二进制的方法是

  1. 将0.5乘以2,得到1.0,将1写在二进制小数点之前,表示整数部分为1。
  2. 将1.0的小数部分0去掉,得到1,表示整数部分为1。

那么所有的将十进制的小数转化为二进制数的方法是:

  1. 将小数部分乘以 2。
  2. 取整数部分,作为二进制小数的下一位。
  3. 小数部分去掉整数部分,重复步骤 1 和步骤 2,直到小数部分为 0位数已达到要求。 例如,将 0.625 转化为二进制小数:
  4. 0.625 × 2 = 1.25,整数部分为 1,小数部分为 0.25。
  5. 0.25 × 2 = 0.5,整数部分为 0,小数部分为 0.5。
  6. 0.5 × 2 = 1.0,整数部分为 1,小数部分为 0。
  7. 停止计算,得到 0.101。 因此,0.625 的二进制小数为 0.101。 需要注意的是,二进制小数位数可能无限循环,因此在实际计算中可能需要对结果进行舍入。

所以上述的结果是:

S=0

M=1.011

E=2

这边对M,E的数都有要求。

对于M:

IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时侯,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位孚点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。这样可以使得float类型的数据保存得精度更高

但是这样的写法会导致有的浮点数在内存中不能被精确保存。如下:

 所以并不能指望所有的浮点数都是在内存中能精确保存,有些数字就是有误差

而对于E:

情况就比较复杂,首先,E为一个无符号整数(unsigned int)。这意味着,如果E为8位,它的取值范围为0-255;如果为11位,它的取值范围为0-2047。但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间是127;对于11位的E,这个中间数是1023。比如,2^10的E是10。所以保存成32位浮点数时,必须保存成10+127=137,即10001001(137的二进制表示放在E位)。

然后,指数E从内存中取出还可以再分成三种情况

1.E不全为0或不全为1

常规情况,怎么存进去,怎么拿出来

比如:

0.5(1/2)的二进制形式为0.1,由于规定整数部分必须为1(M),即将小数点右移1位,则为1.0*2^(-1),其阶码为-1+127=126,表示为01111110,而尾数1.0去掉整数部分为0,补齐0到23位00000000000000000000000,则其二进制表示形式为

0      01111110    00000000000000000000000

S         E            M

上述位置分别对应如上所示。

2.E全为0

他表示的是正负无穷接近于0的数字

 这时,浮点数的指数等于1~127(或者1-1023) 即为真实值,有效数字M不再加上第一位的1,而是还原为0xxxxxx的小数。这样做是为了表示+-0,以及接近于0的很小的数字。

3.E全为1

数很大

理解了上述关系,那么回过头来看类型不匹配导致的打印结果为何是那样。

总结

怎么存float类型的数据:

将5.5转为二进制数,如何转呢,先转整数位。除二取商,再除二,直到商为0,倒序输出得到它的二进制序列数。

然后按v=(-1)^s*M*2^E

其中s看它是正数还是负数,s=0或1;

m就是它的二进制数写成科学计数法形式,1.几几几

这个m在存的时候也有讲究,比如5.5在写成二进制的101.1后,要写成1.011,那么在存的时候

会省去整数1然后先存符号位,再将e的数+127(8位),将他的值转为二进制的数,跟在符号位的

后面,然后写这个小数的小数位跟在后面,最后补0;就将这个数5.5存了进去。

  切忌:float也有范围,不能写-130+127这种,极端例子不要

如何取float型数据

常规情况,怎么存进去,怎么拿出来,按 E的不同来区分 取出。

那么,明白上述回过头来再去分析为何类型与输出不匹配的结果是那样

#include<stdio.h>
int main()
{
	int n = 9;
	float* pFloat = (float*)&n;
	printf("n的值为: %d\n", n);
	printf("*pFloat的值为: %f\n", *pFloat);

	*pFloat = 9.0;
	printf("num的值为: %d\n", n);
	printf("*pFloat的值为: %f\n", *pFloat);
	return 0;
}

输入整型,以浮点型输出:

输入时,9在内存的补码形式是00000000000000000000000000001001,那么在输出时,然后站在float*的角度解读这段二进制序列,那么编译器就认为是0(S) 00000000(E) 00000000000000000001001(M)这样一个数,所以S=(-1)^0,E=1-127,M=00000000000000000001001,所以他表示的数是(-1)^0 * 0.000000000000000000001001 * 2^(-126)

又因为%f打印,只打印小数点后6位,单从M来看为0.000000000000000000001001,前六位已经为0,更何况后面还有2^(-126)更小,所以结果就为0。

输入浮点型,以整型输出

存进去时,浮点数的9.0,它的二进制表示是1001.0

科学计数法表示是1.001 * 2 ^3

这里面,s=0;

e=3;存的时候是3+127

m=1.001;存的时候是001,然后后面补0

存进去是0 100000010 001000000000000000000000

第一个0表示是正数,第二个100000010表示是3+127=130;然后这个数表示的就是130的二进制形式,然后后面是小数点后的数,再补0到共32位是01000001 00010000 00000000 00000000 

在打印时,站在指针角度,要以%d形式打印,所以讲01000001 00010000 00000000 00000000视为整型,转化为十进制打印输出。

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

温有情

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值