在学习C语言的过程中,也许大家都会遇到很多char类型数据和int类型数据打交道的,新手在处理时往往会忽视运算符的规则,表面上程序跑对了,背地里啥也不懂,当程序出bug时又一头雾水,今天我就来给大家揭开关于数据的神秘面纱。
有些表达式的操作数在求值的过程中可能需要有自身类型转换为其他类型。这期间就会发生隐式类型转化。C的整型算术运算总是至少以缺省整型类型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度 一般就是int的字节长度,同时也是CPU的通用寄存器的长度。 因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
在解释如何整形提升之前,先给大家解释一下计算机的数据存储规则:
原码、反码、补码
计算机中的整数有三种表示方法,即原码、反码和补码。 三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位 负整数的三种表示方法各不相同。
原码
直接将二进制按照正负数的形式翻译成二进制就可以。
反码
将原码的符号位不变,其他位依次按位取反就可以得到了。
补码
反码+1就得到补码。
正数和无符号数的原、反、补码都相同。
对于整形来说:数据存放内存中其实存放的是补码.
为什么呢? 在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
如何进行整体提升呢?
整形提升是按照变量的数据类型的符号位来提升的
整形提升的例子:
//负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
//正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
//无符号整形提升,高位补0
一句话总结就是:整形提升前面补0或者补1主要看自身类型,如果变量是无符号数补0,如果是有符号数补符号位
下面我们看几个例子理解一下:
例1
int main()
{
char b = 128;
printf("%dn", b);//-128
//-128和128的以8个bite存储的2进制序列一样,整形提升后都补的是char类型的符号位1
//128被计算机解释成-128
printf("%un", b);
return 0;
}
例2
int main()
{
int i = -20;
//原码1000 0000 0000 0000 0000 0000 0001 0100
//反码1111 1111 1111 1111 1111 1111 1110 1011
//补码1111 1111 1111 1111 1111 1111 1110 1100
unsigned int j = 10;
//0000 0000 0000 0000 0000 0000 0000 1010
printf("%dn", i + j);//-10
//发生隐式类型转化(不改变二进制序列),int变成unsigned,
//1111 1111 1111 1111 1111 1111 1111 0110 %d将其解释成负数,即补码要向原码转化
//1000 0000 0000 0000 0000 0000 0000 1010:-10
return 0;
}
这个例子如果是初学C语言,相比一定能自信的回答出正确答案,但是背后的门道可深着呢。
例3
int main()
{
char a = -128;
//原码1000 0000 0000 0000 0000 0000 1000 0000
//反码1111 1111 1111 1111 1111 1111 0111 1111
//补码1111 1111 1111 1111 1111 1111 1000 0000
//a为char类型,发生截断存储,存储低8位bite,即1000 0000
printf("%un", a); //取出数据时发生整形提升,看自身类型补符号位1,即原来补码
//以无符号整形输出(数据大小不变,看待方式变化),发生隐式类型转化,忽略符号位
//无符号数原反补相同,1111 1111 1111 1111 1111 1111 1000 0000表示4294967168
return 0;
}
例4
int main()
{
char a = 0xb6;
short b = 0xb600;
int c = 0xb6000000;
if(a==0xb6)
printf("a");
if(b==0xb600)
printf("b");
if(c==0xb6000000)
printf("c");
return 0;
}
例4中的a,b要进行整形提升,但是c不需要整形提升 a,b整形提升之后,变成了负数,所以表达式 a==0xb6 , b==0xb600 的结果是假,但是c不发生整形提升,则表达式 c==0xb6000000 的结果是真. 所程序输出的结果是: c
通过这几个例子,想必大家对于隐式类型转化和整形提升以及牵扯到的数据类型的存储有了更深的了解,这里谈谈我的理解,为什么发生整形提升通俗的讲其实就是计算机根据类型对于一段相同的二进制序列有了不同的解释。在存储的过程中,计算机根据类型大小会对数据的二进制序列发生截断或者完整存储;在取数据的过程中,计算机有根据不同的要求解释数据,如char类型数据被需求解释为整形,就要整形提升,数据就有可能变化,但是如果还按照char类型解释,则数据不变。
下面我们谈谈浮点数的存储:
整形和浮点数的存、取规则不同(浮点数存储有精度损失)。任意一个二进制浮点数V可以表示成下面的形式:
V=(-1)^S * M * 2^E (-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。 M表示有效数字,大于等于1,小于2。 2^E表示指数位。例如十进制的-5.0,写成二进制是 -101.0 ,相当于 -1.01×2^2 。那么,s=1,M=1.01,E=2。
浮点数存储规则思路,先将浮点数转化成标准二进制科学计数法,求出s,m,e,然后将s,m,e按上图存储,取数据时按存储模型取出s,m,e对应的二进制序列,然后转化成数值带入公式转化成浮点数,那么问题就来到了这三部分是怎么存储的?
M的存储
1≤M<2 ,M可以写成 1.xxxxxx 的形式,其中xxxxxx表示小数部分。 IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的 xxxxxx部分。比如保存1.01的时 候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位, 将第一位的1舍去以后,等于可以保存24位有效数字。
E的存储
首先,E为一个无符号整数(unsigned int)
这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为0~2047。但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。
E的存储[0,255] E的真实[-127,128]
0000 0000 -127
1111 1111 128
E为全0(又被称为浮点数的0值,浮点数的0值为一个区间[-min,min],因此用浮点数时不能直接与0比较,而是规定误差范围)E的真实值为-127,一个数乘无限小的数结果无限接近0
E为全1,E的真实值为128,浮点数无限大
例题
int main()
{
int n = 9;
float* pFloat = (float*)&n;
printf("n的值为:%dn", n);//9
printf("*pFloat的值为:%fn", *pFloat);//0.000000
//9的二进制序列为0000 0000 0000 0000 0000 0000 0000 1001
//按照浮点数存储规则解释
//0(符号位S)000 0000 0(指数E)000 0000 0000 0000 0000 1001(有效数字M)指数为-127
//(-1)^0 × 0.00000000000000000001001×2^(-127)=1.001×2^(-147)约等于0.000000
*pFloat = 9.0;
printf("num的值为:%dn", n);//1091567616
//9.0 -> 1001.0 ->(-1)^01.0012^3 -> s=0, M=1.001,E=3+127=130
//0(S) 10000010(E) 001 0000 0000 0000 0000 0000(M)
//按整形解释就是1091567616
printf("*pFloat的值为:%fn", *pFloat);//9.000000
return 0;
}
以上就是关于隐式类型转化、整形提升和数据存储有关部分的知识介绍了,如果有什么错误请大家及时点出!



