有关混合数据类型转换的一些想法
有关混合数据类型转换的一些想法
一:强制数据类型转换
运用强制类型转换运算符: (数据类型)
举例:int i = (int)1.6 + (int)1.7;首先,1.6和1.7转换成整数1,然后再相加得2,最后赋值给i。
二:自动数据类型转换
数据类型级别(从高至低):long double > double > float > unsigned long long > long long > unsigned long > long > unsigned int > int;当long和int的级别相同时,unsigned int比long的级别高。
1.升级:从较小数据类型转换到较大数据类型
1).赋值表达式
举例:
#include <stdio.h>
int main(void)
{
char ch;
int i;
float fl;
fl = i = ch = ‘C’;
printf(“ch = %c, i = %d, fl = %.2f\n”, ch, i, fl);
return 0;
}
fl = i = ch = ‘C’;这是一个赋值表达式。字符’C’被赋值给变量ch,然后再赋值给变量i;最后赋值给变量fl。变量i接受由字符’C’转换的整数,按4字节存储67(字符’C’对应ASCII中67);变量fl接受由字符’C’转换的浮点数,先将字符’C’转换成整数67,然后再转换成浮点数67.00,按4字节存储。
2). 函数参数的传递
举例:
#include <stdio.h>
int main(void)
{
float fl = 2.3;
printf(“%.2f\n”, fl);
return 0;
}
当作为函数参数传递时,float类型被转换成double类型。在本例中,函数参数中float类型的参数fl(2.3)被自动转换成double类型,然后被读取。同样,当作为函数参数传递时,char类型和short类型被自动转换成int类型。注意,实参传递的过程,也是给变量赋值的过程。
3).混合数据类型的运算
举例①:
#include <stdio.h>
int main(void)
{
int i = -20;
unsigned int j = 10;
printf(“%d\n”, i + j);
printf(“%u\n”, i + j);
return 0;
}
在计算机中,有符号类型都是用补码存储的,用补码进行运算,有符号整数-20的补码为1111 1111 1111 1111 1111 1111 1110 1100。在计算表达式i + j时,其中较小的数据类型会从较小数据类型转换到较大数据类型,即int类型的变量i会转换成同变量j一样的数据类型unsigned int。按照unsigned int数据类型来读取二进制1111 1111 1111 1111 1111 1111 1110 1100,即4294967276,再与10相加,即为4294967286。
举例②:
#include <stdio.h>
int main(void)
{
unsigned int i = 3;
unsigned int j;
j = i * (-1);
printf(“%u\n”, j);
return 0;
}
由于unsigned int类型级别高于int类型,所以在作乘法运算i*(-1)时,有符号整数-1将被自动转换成unsigned int类型,-1的补码为
1111 1111 1111 1111 111 1 111 1 111 1 1111,将其按unsigned int类型来读取,值为4294967295。i*(-1)=3*4294967295=12884901885,12884901885的二进制数为
10 1111 1111 1111 1111 1111 1111 1111 1101,取后32位,然后转换成十进制数为4294967293,也就是变量j的值。
2.降级:从较大数据类型转换到较小数据类型
举例:
#include <stdio.h>
int main(void)
{
char ch;
ch = 1107;
printf(“%c\n”, ch);
ch = 80.89;
printf(“%c\n”, ch);
return 0;
}
赋值语句ch = 1107;将变量ch设置为一个超出其类型范围的值,先将1107写成二进制数100 0101 0011,取后8位0101 0011,转换成char类型为‘S’。
或者可以这样计算,char类型为1字节,即8位,这8位可以表示256个字符,以256为模,将1107 % 256 = 83,转换成char类型为‘S’。
当浮点类型被降级为整形类型时,原来的浮点值会被截断。在本例中,赋值语句ch = 80.89;先将浮点类型80.89截断为整数类型88,然后转换成char类型字符‘C’,再赋值给变量ch。
三.格式化数据类型转换
根据国际标准IEEE 754,任意一个二进制浮点数V可以表示成下面的形式:
(1). 表示符号位,当S = 0,V为正数;当S = 1,V为负数;
(2). M表示有效数字,大于等于1,小于2。
(3). 表示指数位;
举例①:
十进制的5.0,写成二进制是101.0,相当于(-1)^0×1.01×2^2。那么,按照上面V的格式,可以得出s=0,M=1.01,E=2。
举例②:
十进制的-5.0,写成二进制是-101.0,相当于(-1)^1×1.01×2^2。那么,s=1,M=1.01,E=2。
IEEE 754规定,对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。
图一
对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
图二
IEEE 754对有效数字M和指数E,还有一些特别规定:
(1). 前面说过,1≤M<2,也就是说,M可以写成1.xxxxxx的形式,其中xxxxxx表示小
数部分。IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,就可以保存24位有效数字。
(2). 首先,E为一个无符号整数(unsigned int)。这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为0~2047。但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,E的真实值(即8位二进制数)必须再减去一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。
举例:
2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001,即E的真实值位10001001。
其次,指数E还可以再分成三种情况:
①E不全为0且不全为1。这时,浮点数就采用上面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。
②E全为0。这时,浮点数的指数E等于1-127(或者1-1023),有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
③E全为1。这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);如果有效数字M不全为0,表示这个数不是一个数(NaN)。
对于图一,S = 0;E的真实值为01111100,化为整数为124,减去127得-3,即E = -3;M =1.01;则V=,-1的指数为0,表示V是正数,将V化为10进制数=0.15625。
1.有符号类型数格式化为无符号类型输出
举例:
#include <stdio.h>
int main(void)
{
int i = -1;
printf(“%u\n”, i);
return 0;
}
变量i为有符号类型,对于有符号类型,计算机都是使用补码进行表示或者存储的,-1的补码是1111 1111 1111 1111 1111 1111 1111 1111(注:符号位不取反),将这个二进制数按无符号%u来读取,即4294967295。
2.int类型格式化为浮点数类型输出
举例:
#include <stdio.h>
int main(void)
{
int num = 9;
flaot * pt = # // int类型和flaot类型都占用32bits
printf(“%d\n”, num);
printf(“%f\n”, *pt);
*pt = 9.0;
printf(“%d\n”, num);
printf(“%f\n”, *pt);
return 0;
}
int类型9的二进制数0000 0000 0000 0000 0000 0000 0000 1001(对应float类型,写成
32位),对照国际标准IEEE 754,S=0;指数E全为0,按照国际标准IEEE 754规定,此时的E=1-127=-126;M=0.0000 0000 0000 0000 0000 0000 0000 1001。因此,将这个int类型的整数按照浮点数float类型的浮点数来读取的话,即为
V=(-1)^0×0.0000 0000 0000 0000 0000 0000 0000 1001×2^(-126)
=1.001×2^(-146)
很显然,这是个很接近0的正数,所以,转换成浮点数就变成了0.000000。
3.浮点数类型格式化为int类型输出
*pt = 9.0;由于flaot为32位,可写成V=(-1)^0×1.001×2^3,其中S=0;M=1.001,;E=3,转换成其真实值为3+127=130,二进制数为1000 0010;将浮点数9.0写成float类型的二进制数为0 10000010 0010 0000 0000 0000 0000 000。将其按照int类型来读取,正好为1091567616。
如果float * pt = &num改成double * pt = &num呢?
举例:
#include <stdio.h>
int main(void)
{
int num = 9;
double *pt = # // int类型占32bits,double类型占64bits
printf(“%d\n”, num);
printf(“%f\n”, *pt);
*pt = 9.0;
printf(“%d\n”, num);
printf(“%f\n”, *pt);
return 0;
}
*pt = 9.0;将浮点数9.0存储在指针pt指向的地址上,由于指针pt指向的地址只具有32bits,而double类型的地址具有64bits。
如果将double *pt = &num取消掉呢?
举例:
#include <stdio.h>
int main(void)
{
int num1 = 9;
double num2 = 9.0;
float num3 = 9.0;
printf(“%d\n”, num1);
printf(“%f\n”, num1);
printf(“%d\n”, num2);
printf(“%f\n”, num2);
printf(“%d\n”, num3);
printf(“%f\n”, num3);
return 0;
}
出现了两种情况,不解。
将double类型的浮点数9.0以国际标准IEEE 754表示,V=(-1)^0×1.001×2^3。其中S=0;M=1.001;E=3,其真实值为3+1023=1026,二进制数100 0000 0010。写成二进制形式为
0 10000000010 00100000000000000000000000000000000000000000000000000,将其按%d读取,截取后32bits,故打印结果为0.000000。
Float类型变量,作为参数给函数传递时,会自动被升级为double类型。
4.栈:函数实参的传递
举例:
#include <stdio.h>
int main(void)
{
float n1 = 3.0;
double n2 = 3.0;
long n3 = 2000000000;
long n4 = 1234567890;
printf(“%ld %ld %ld %ld\n”, n1, n2, n3, n4);
return 0;
}
printf(“%ld %ld %ld %ld\n”, n1, n2, n3, n4);该调用将n1,n2,n3,n4这4个参数传递给函数,并且计算机把传入的值根据其变量的数据类型,而不是根据转换说明的数据类型,把这些值放入栈中。因此,n1被存储在栈中,占64bits;同样,n2也在栈中占用64bits;而n3和n4在栈中分别占用了32bits。printf()函数根据转换说明,而不是根据传入的值的数据类型,从栈中读取。%ld转换说明表示printf()函数应该读取32bits,所以,printf()函数读取了栈中前32bits作为第一个值。然而,这是n1的前半部分,被解释成了一个long类型的整数。printf()函数根据下一个%ld转换说明,printf()函数再读取32bits。这是n1的后半部分,也被解释成了一个long类型的整数。类似的,根据第3个和第4个%ld,printf()函数读取了n2的前半部分和后半部分,并解释成了2个long类型的整数。
综上,由第2条,第3条和第4条可以看出,用指针取地址上的值时,会根据指针指向的数据类型取地址的长度,例如float *pt;取回float类型的32bits地址的值;double *pt;取回double类型的64bits地址上的值。而对于基本变量float a;或double b;在作为参数传入函数时候,直接都被自动升级为了double类型的数据类型,都是64bits。