1.类型转换的相关概念
(1)类型转换分类:自动类型转换(简称“自转”)和强制类型转换(简称“强转”)。
(2)类型提升:编译器对操作数进行运算前,将所有操作数都转换成取值范围较大的操作数类型。
(3)类型提升目的:避免数据信息丢失。因为由于级别高的数据类型比级别低的数据类型所占的内存空间大,可以保持数据类型的精度。
(4)整数提升:char型和short型进行运算前都自动提升为int型,C99和C11中还可以产生向unsigned int型的转换。
(5)类型提升规则:根据操作数类型由低到高进行转换。如图所示,箭头方向表示转换方向,纵向箭头表示必然转换,横向箭头仅表示转换方向不表示转换过程。
比如:一个int型操作数与一个float型操作数进行算数运算,编译器会先将int型直接转换成float型,无需经过int型先转换为unsigned int型再转换为long型的中间过程。
特例:如果一个操作数是long型,另一个是unsigned int型,同时unsigned int型操作数的值又不能用long型表示,则两个操作数都转换为unsigned long型。
2.类型转换的几种情况
(1)赋值中的自动类型转换:赋值运算符左侧(目标侧)变量的类型和右侧表达式的类型不一致,则右侧表达式的值自动转换成左侧变量的类型。
比如:int n = 2; float f = 3.5; double d = n+f,编译器运算n+f时,会将变量n的类型提升为变量f相同的float型,n+f为变量d赋值时,会将float型的运算结果自动转换为double型再赋值给d。
(2)高数据类型转换低数据类型:可能丢失信息。
比如:int转char会丢失高24位,float转int会丢失小数部分(在某些情况下整数部分的精度也会丢失),double转float会丢失小数部分精度(四舍五入)。
(3)低数据类型转换高数据类型:改变数据形式,不会丢失信息。
(4)无符号数据类型(unsigned型)和有符号数据类型(signed型)之间的转换
①unsigned和signed都只能定义整型数据(short,int,long)和字符型数据(char型可以看成特殊的int型),所以unsigned型和signed型的转换只针对整型数据。
②unsigned型转换为其他类型数据:转换为同字节数据类型时不改变数据内容,转换为低字节数据类型时截取低字节内容,转换为高字节数据类型时高位补0。
③signed型转换为其他类型数据:转换为同字节数据类型时不改变数据内容,转换为低字节数据类型时截取低字节内容,转换为高字节数据类型时补符号位(负数高位补1,非负数高位补0)。
④unsigned型和signed型转换数据类型时,数据变化只与字节大小有关,和目标数据类型无关。
以short型和unsigned short型为例,代码如下。
/*short型*/
int main(int argc, const char *argv[])
{
char a0 = 0;
unsigned char a1 = 0;
short b0 = 0;
unsigned short b1 = 0;
int c0 = 0;
unsigned int c1 = 0;
b0 = -1; /*负数在内存中用二进制补码存储,-1的补码1111 1111 1111 1111*/
b1 = (unsigned short)b0; /*转换为同字节数据类型unsigned short时不改变数据内容*/
a0 = (char)b0; /*转换为低字节数据类型char时截取低8位内容*/
a1 = (unsigned char)b0; /*转换为低字节数据类型unsigned char时截取低8位内容*/
c0 = (int)b0; /*转换为高字节数据类型int时负数高位补1*/
c1 = (unsigned int)b0; /*转换为高字节数据类型unsigned int时负数高位补1*/
printf("char:%d\n", a0);
printf("unsigned char:%u\n", a1);
printf("short:%d\n", b0);
printf("unsigned short:%u\n", b1);
printf("int:%d\n", c0);
printf("unsigned int:%u\n", c1);
return 0;
}
/*unsigned short型*/
int main(int argc, const char *argv[])
{
char a0 = 0;
unsigned char a1 = 0;
short b0 = 0;
unsigned short b1 = 0;
int c0 = 0;
unsigned int c1 = 0;
b1 = 65535; /*无符号数65535的原码1111 1111 1111 1111*/
b0 = (short)b1; /*转换为同字节数据类型short时不改变数据内容*/
a0 = (char)b0; /*转换为低字节数据类型char时截取低8位内容*/
a1 = (unsigned char)b0; /*转换为低字节数据类型unsigned char时截取低8位内容*/
c0 = (int)b0; /*转换为高字节数据类型int时高位补0*/
c1 = (unsigned int)b0; /*转换为高字节数据类型unsigned int时高位补0*/
printf("char:%d\n", a0);
printf("unsigned char:%u\n", a1);
printf("short:%d\n", b0);
printf("unsigned short:%u\n", b1);
printf("int:%d\n", c0);
printf("unsigned int:%u\n", c1);
return 0;
}
两段代码运行结果相同,如图所示。
可以看出,short型数据b0为-1时,转换为低字节的char型或unsigned char型,都只是截取低8位数据,转换为高字节的int型或unsigned int型,都是补符号位1。unsigned short同理,低位截取,高位补0。
3.负数的无符号输出
(1)定义char a0= -1,测试a0的无符号输出内容,代码如下。
int main(int argc, const char *argv[])
{
char a0 = -1;
printf("a0-d:%d\n", a0);
printf("a0-u:%u\n", a0);
return 0;
}
运行结果如图所示。
负数是在内存中以补码的形式存储,a0变量存储的二进制数据是1111 1111,也就是无符号数255,因此a0的%u输出本应该是255(即2^8-1),然而运行结果是4294967295(即2^32-1)。
这个错误的输出结果和printf函数以及类型转换的方式有关系。当使用printf进行打印时,printf会在内存中分配一段buffer用于存储打印的内容,该段buffer以4个字节为单位存储数据,所以当char型变量a0的值传入printf函数后,会自动进行类型提升,从1字节数据提升为4字节数据。根据类型提升的规则,1字节数据-1转为4字节数据时,会补符号位1,因此printf缓存中存储的二进制数据为1111 1111 1111 1111 1111 1111 1111 1111,用%d打印这段数据是-1,用%u打印这段数据是4294967295,而不是255。
(2)如果想要用%u打印a0的内容,需要在打印前将a0进行类型转换,代码如下。
int main(int argc, const char *argv[])
{
char a0 = -1;
printf("a0-d:%d\n", a0);
printf("a0-u:%u\n", (unsigned char)a0); /*以无符号格式输入a0*/
return 0;
}
运行结果如图所示。

(3)同理,定义short b0= -1,printf用%d打印这段数据是-1,用%u打印这段数据是4294967295,而不是65535(即2^16-1),如果想要用%u打印b0的内容也需要进行类型转换。
(4)此外,某些编译器下unsigned char a1= -1不会提示warning也不会报错,因为负数是在内存中以补码的形式存储,所以可以看作a1变量存储的是无符号数据255。
4.其他
(1)关于printf函数打印float型数据时读写缓存的方式,代码如下。
int main(int argc, const char *argv[])
{
int num0 = 0;
int num1 = 0;
int num2 = 0;
float dec0 = 0;
num0 = 1;
num1 = 2;
num2 = 3;
dec0 = 9.8;
printf("float byte:%d, int byte:%d\n", sizeof(float), sizeof(int));
printf("%f, %d, %d, %d\n", dec0, num0, num1, num2);
printf("%d, %d, %d, %d\n", dec0, num0, num1, num2);
printf("%d, %d, %d, %d\n", num0, dec0, num1, num2);
return 0;
}
在32位编译器下运行这段代码,结果如图。
在64位编译器下运行这段代码,结果如图。
可以看出,无论是使用32位编译器还是64位编译器,float型和int型的字节大小都是4,使用%d输出浮点型都会打印错误数据。然而在32位编译器下,输出浮点数dec0错误后,会影响后续整型num0等数据的输出,但在64位编译器下不会影响后续输出。
猜想:C语言浮点数在内存中是以阶码+尾数的形式存储,虽然float型数据大小是4个字节,但printf将float型数据的阶码和尾数分别放在两个4字节大小的内存中,即printf用8字节内存储存float型数据。在32位编译器下系统一次能读取4字节数据,以%f形式输出浮点数时,系统会进行两次读取,然后合成为小数并打印,以%d形式输出浮点数时,系统只会进行一次读取,所以导致内存读取错位,进而影响后续数据输出;在64位编译器下系统一次能读取8字节数据,以%f形式输出浮点数时,系统会只进行一次读取,以%d形式输出浮点数时,系统也只会进行一次读取,但由于能一次读取8字节数据,所以不会导致内存读取错位,也不会影响后续输出。
由于printf函数涉及到的内容非常复杂,在不同编译器下也会有差异,且对printf函数研究并不深刻,所以仅供参考。
本文详细介绍了C语言中的类型转换,包括自动类型转换、强制类型转换和类型提升,特别讨论了整数提升的规则。在转换过程中,可能会涉及到信息丢失的问题。此外,文章还探讨了负数的无符号输出问题,解释了负数在内存中的存储形式以及printf函数在不同类型转换中的行为。最后,通过实例展示了不同编译器下printf函数打印float型数据时的差异。



6979

被折叠的 条评论
为什么被折叠?



