C语言之类型转换

279 阅读10分钟

表达式求值—类型转换

表达式求值的顺序一部分是有操作符的优先级和结合性决定。

同样,有些表达式的操作数在求值过程中可能需要转换为其他类型。

类型转换

C语言是一种强类型语言,当使用一种类型代替另外一种类型进行操作时或者存在两个不同类型的对象进行操作时,首先就得进行类型的转换然后再说其他。而类型转换的方式一般可分为隐式类型转换(也称:自动类型转换)和显示类型转换(也称:强制类型转换),两者有着本质上的区别。

隐式类型转换是由==编译器自动进行==的,不需要人为的干预,而且我们也观察不到类型是如何进行转换的,所以被称为:“ 隐式 ”。而显式类型转换是由==程序员明确指定==的,所以才被称为:“ 强制类型转换 ”。

1.1显式类型转换

显式类型转换,即强制类型转换,需要程序员在代码中明确指定。

显式转换是指将一个较大范围的数据类型转换为较小范围的数据类型时,或者将一个对象类型转换为另一个对象类型时,需要使用强制类型转换符号进行显示转换,强制转换会造成数据丢失。

实例1:将int类型的变量赋值给byte类型的变量,需要显示类型转换

int i0;
byte b=(byte)i;// 显式转换,需要使用强制类型转换符号

实例2:将double类型变量强制转化为整数类型

double a=3.14;
int b=(int)a;int intValue = 42;// 强制从 double 到 int,数据可能损失小数部分

实例3:将int类型强制转换为浮点型:

int intValue = 42;
float floatValue = (float)intValue; // 强制从 int 到 float,数据可能损失精度
实例:

将int类型强制转换成double类型.

#include <stdio.h>
 
int main()
{
   int sum = 17, count = 5;
   double mean;
   mean = (double) sum / count;
   printf("Value of mean : %f\n", mean );
 
}

结果:

屏幕截图 2024-11-09 185257.png 这里要注意的是强制类型转换运算符的优先级大于除法,因此 sum 的值首先被转换为 double 型,然后除以 count,得到一个类型为 double 的值。

类型转换可以是隐式的,由编译器自动执行,也可以是显式的,通过使用强制类型转换运算符来指定。在编程时,有需要类型转换的时候都用上强制类型转换运算符,是一种良好的编程习惯。

1.2隐式类型转换

隐式转换是不需要编写代码来指定的转换,编译器会自动进行。

隐式转换是指将一个较小范围的数据类型转换为较大范围的数据类型时,编译器会自动完成类型转换。

有些表达式的操作数在求值的过程中可能需要转换为其他的类型,而这种你不知晓的类型上的转换通常分为两种情况:整型提升运算转换

在整型提升和运算转换之前还有一个知识点:截断操作

截断操作

什么是截断操作?

下面举个例子:char c = -1 ;。字面常量-1时如何储存到char变量c中去的呢?注意这里的字面常量-1本质上是一个整数,而一个整数所占的内存空间是32个二进制位,所以这时-1在内存中的补码如下图所示。一个字符变量c只能存放1个字节(即:8个二进制位),怎么可能放-1呢?。

所以这时就要发生截断,截断的规则:挑低位的字节数,放置到需要截断存储的变量中去

截断操作.png

1.2.1整型提升

整型提升是C程序设计语言中的一项规定:在表达式计算时,各种整型首先要提升为int类型,如果int类型不足以表示则要提升为unsigned int类型;然后执行表达式的运算。

整型提升的意义:

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。

因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。

通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。

所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。

整型提升的前提条件:只有当表达式中出现长度可能小于int类型的整型值时,才需要对改值进行整型提升转换为int类型或unsigned int类型,然后才能送入CPU去执行运算。

例题1:代码如下
#include<stdio.h>
int main()
{
	char a = 5;
	char b = 126;
	char c = a + b;
	printf("%d", c);

	return 0;
}

结果是什么呢?会是131吗?

看看结果:

屏幕截图 2024-11-09 194958.png 发现结果并不是131,为什么呢?

解析:怎么算的呢?

我们一步一步的算

char a=5;

这里5是整数,整数类型所占的内存空间时32比特位,所以5在内存中的二进制序列为00000000 00000000 00000000 00000101 但这里用来存放常量5的变量是char类型,所占的内存空间为8比特位,这时候C语言就会将其截断,截断的规则将最低位的一个字节截断存储在a里面 这时候a里面存的就是00000101

char b=126;

同理126的二进制序列是00000000000000000000000001111110 存到b中为01111110

char c=a+b;

那么a和b如何相加呢?

这里a和b都是char类型,它的大小没有达到整形的大小,计算机为了提升计算精度这个时候会整形提升,如何提升的?

a为 00000101 提升后为 00000000 00000000 00000000 00000101

b为 00000101 提升后为 00000000000000000000000001111110

注意:并不是将高位补0就完事了,这里补0是因为他们的符号位都为0也就是都为正才补0,如果为负符号位就为1,那么就补1.

然后因为c是char类型所以又要截断 这个时候c里面就存的 100000011

printf(“%d”,c); 注意这里打印c的时候他的转换说为%d,意思是打印一个整形,但是c是char类型,这个时候我们又要整形提升了,这里它的符号位为1,那么就高位补1: 11111111111111111111111110000011 我们在内存中存的是补码,打印的是原码,所以还要补码转原码 先转为反码 11111111111111111111111110000010 再转为原码 10000000000000000000000001111101 最后打印为-125

屏幕截图 2024-11-09 201312.png 通过计算机我们可以知道:

屏幕截图 2024-11-09 200949.png

例题2:代码如下
#include<stdio.h>
int main()
{
	char a = 0x86;
	short b = 0xb600;
	int c = 0xb6000000;

	if (a == 0x86)
		printf("a");
	if (b == 0xb600)
		printf("b");
	if (c == 0xb6000000)
		printf("c");

	return 0;
}

结果:只有c被成功打印

屏幕截图 2024-11-09 211119.png 解析:先拿char型变量a来说吧,首先我们知道a截断存储了十六进制数0xb6,内存补码为:10110110。

但当执行到if语句的时候,a需要进行关系运算时需要进行有符号的整形提升,所以整型提升时应该在高位补符号位,结果为: 11111111 11111111 11111111 10110110,与0xb6的二进制序列: 00000000 00000000 00000000 10110110当然不一样呀,所以if(a == 0xb6)判断的结果自然为假并不会打印a了呀。 同理于short类型的变量b也是如此并不会被打印,而int类型的变量c由于其并不用进行整形提升,所以判断结果为真打印了c。

例题3:代码如下
#include<stdio.h>
int main()
{
	char c = 1;
	printf("%u\n", sizeof(c));
	printf("%u\n", sizeof(+c));
	printf("%u\n", sizeof(-c));
    //%u表示数据按十进制无符号整型数输入或输出
	return 0;
}

结果:

屏幕截图 2024-11-10 095411.png 很多人会觉得这道题的结果是两个1,但值得注意的是上面这个例子中 sizeof(+c) 计算的是+c这个表达式值的类型所占的内存空间的大小,而+c表达式的值是c进行整形提升后的结果,所以sizeof的结果为4个字节,同理表达式-c也会发生生整形提升,所以sizeof(-c)是4个字节。但是sizeof(c)就是1个字节。

1.2.2算术转换

我们刚刚讨论的是类型小于整形的情况,而算术转换是用来处理这些大于等于整形的情况。如果某个操作符的各个擦作数属于并一同的类型,那么计算是无法进行下去的,除非将这些操作数全部转化成同一类型。寻常算数转换的方向:

int–>unsigned int–>unsigned long int>fioat–>double–>long double.

如果某个操作数的类型在上面的表中排行较低,那么首先要转化成另外一个操作数的类型后执行运算。

警告:但是算术转换要合理,要不然存在一些潜在的问题

float f = 3.14;
int num = f;//隐式转换,可能会有数据丢失
易错实例:代码如下
#include<stdio.h>
int main()
{
	int i = -2;
	printf("%u\n", sizeof(i));
	if (i < sizeof(i))
	{
		printf("hhh\n");
	}
	else
		printf("hehehe\n");
	return 0;
}

结果是打印什么呢?是hhh还是hehehe?有人可能会说打印的肯定是hhh,因为sizeof(i)的结果是4,必然大于-1。真的是这样吗?我们来看看结果:

屏幕截图 2024-11-10 102422.png

可以看到结果打印了hehehe,为什么会这样呢?

这是因为在执行if(i<sizeof(i)) 判断的时候隐蔽发生了算数转换,将i的类型从int型转换成了 unsigned int 型,我们知道int型 -1的补码在 unsigned int 下是一个非常大的整数必然远大于 sizeof(i) 计算出来的4,所以打印结果为hahaha。那为什么i会发生算数转换呢? 这是因为 sizeof() 表达式的返回值的类型其实是unsigned int型的。