C语言中表达式求值和整形提升的那些坑那些事

在这里插入图片描述

内存中数据的范围

我们知道数据有很多种类型,阔以看看我这篇文章 数据在内存中的存储 里面详细介绍了各种类型的数据。下面都是在32位平台下分析的。
我们先以char类型为例子,char类型占一个字节,看看它的范围是多少,图片如下:
在这里插入图片描述
因此我们可以推导出其他类型的范围:

类型存储空间大小数据范围
char1个字节-128 ~ 127
unsigned char1个字节0 ~ 255
short2个字节-32 768 ~ 32 767
unsigned short2个字节0 ~ 65535
int4个字节-2147483648 ~ 2 147 483 647
unsigned int4个字节0 ~ 4 294 967 295

表达式求值

表达式求值的顺序一部分是由操作符的优先级和结合性决定。同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。我们要了解新的概念:

1.整型提升

那什么是整形提升呢?定义如下:
C语言的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整形提升的意义:

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算

//实例:
char a,b,c;
...
a = b + c;

b和c的值被提升为普通整型,然后再执行加法运算。
加法运算完成之后,结果将被截断,然后再存储于a中

那么如何进行整形提升呢?这就和符号位有关了。
整形提升是按照变量的数据类型的符号位来提升的。

正数的整形提升:
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001

负数的整形提升:
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111

无符号整形提升:
即相当于正数整形提升,高位补0。

整形提升的例子:
实例1:
在这里插入图片描述
实例1中的a,b要进行整形提升,但是c不需要整形提升 ;0xb6写成二进制就是10110110,可以看出符号位是1,所以要在前面补1凑到32位重新,从而a,b整形提升之后,变成了负数,就不等于原来的数据了。
所以表达式a== 0xb6 , b== 0xb600 的结果是假,但是c不发生整形提升,则表达式 c=0xb6000000 的结果是真。
实例2:
在这里插入图片描述
c只要参与表达式运算,就会发生整形提升,表达式 +c ,就会发生提升,所以 sizeof(+c) 是4个字节.表达式 -c 也会发生整形提升,所以 sizeof(-c) 是4个字节,但是 sizeof( c ) ,就是1个字节。而!操作符不需要操作数在求值的过程中转换为其他类型,仍然是char型。

2.算术转换

如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。

  • 类型排名从高到低
  • long double
  • double
  • float
  • unsigned long int
  • long int
  • unsigned int
  • int

如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。也就是说执行表达式最终的结果类型就是排名最高的那个。
比如:
若有以下定义: char a; int b;float c; double d; 则表达式a * b + d - c值的类型为double。
注意: 但是算术转换要合理,要不然会有一些潜在的问题。

float f = 3.14;
int num = f;//隐式转换,会有精度丢失

3.操作符的属性

复杂表达式的求值有三个影响的因素。

  1. 操作符的优先级
  2. 操作符的结合性
  3. 是否控制求值顺序。

两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们操作符优先级。点击这里有详细操作符的图片-> 操作符优先级图片
一些定义我们先了解到这里,接下来我们做一下题目来体验一下吧。

题目练习

1.

//输出什么?
#include <stdio.h>
int main()
{
char a= -1;
signed char b=-1;
unsigned char c=-1;
printf("a=%d,b=%d,c=%d",a,b,c);
return 0;
}

我们第一次看这到题时我们发现把 -1一个整数放到char类型中有点意思。
我们先写出整数-1的原、反和补码吧。

整数-1
原码:10000000 00000000 00000000 00000001
反码:11111111 11111111 11111111 11111110
补码:11111111 11111111 11111111 11111111

因为a是char类型的数据,大小是一个字节,只能存放8个比特位。所以发生了截断,只把补码的后面8位放到a中,因此a在内存中存放的是 11111111 8位1。
但是我们却以整数的方式来看待,想要打印出来,所以要发生整形提升而整形提升又要看自己所属的类型是有符号类型还是无符号类型。
题目中a和b都是有符号类型,所以符号位就是1,整形提升结果为:11111111 11111111 11111111 11111111 32位1。
题目中c是无符号类型,所以在前面补0即可,整形提升结果为:00000000 00000000 00000000 11111111 。

我们以整数的方式来看待,但是是有符号的还是无符号的呢,显然 %d 就是以整数并且是有符号的整数来看待。所以我们还要把补码还原到原码。
对于a和b符号位是1,所以是负数,还原为原码为:10000000 00000000 00000000 00000001。(-1)
对于c符号位是0,所以是正数,原码等于补码,即就是:00000000 00000000 00000000 11111111 。(255)
验证答案如下:
在这里插入图片描述
2.

//输出什么?
#include <stdio.h>
int main()
{
	char a = -128;
	char b = 128;
	printf("a=%u\n", a);
	printf("b=%u\n", b);
	return 0;
}

这道题和上一道题很类似,也是把整数放到字符类型char里面。
分析如下:
先写出 -128和128 的原、反和补码

整数 -128
原码:10000000 00000000 00000000 10000000
反码:11111111 11111111 11111111 01111111
补码:11111111 11111111 11111111 10000000
整数 128
原码:00000000 00000000 00000000 10000000
反码:00000000 00000000 00000000 10000000
补码:00000000 00000000 00000000 10000000

同样的道理,char类型放不下 32位数据,只能存放8位,所以发生了截断;只把低8位存放到a和b中,所以在a、b中存放的是 10000000 8位。
但是我们却要以整数的方式来打印出来,所以要整形提升,整形提升又要看自己所属的类型是有符号还是无符号。
因为a和b是有符号类型,所以最高位1就是符号位,整形提升在前面补1凑到32位,变成
11111111 11111111 11111111 10000000。并且我们是以无符号整数%u 来看待补码的,
所以原码等于补码,即最终结果就是 11111111 11111111 11111111 10000000 是一个非常非常大的数字。
答案验证:
在这里插入图片描述

3.

//输出什么?
int main()
{
	int i = -20;
	unsigned int j = 10;
	printf("%d\n", i + j);
	return 0;
}

题目是一个有符号的int型和一个无符号的int型相加,我们不妨先写出他们的原、反和补码,然后补码相加即可。

整数 -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
两者相加后的补码:
11111111 11111111 11111111 11110110

我们算出 i+j 在内存中存的数据了,接近我们分析是以有符号还是无符号的目光来看待这个补码了。按照算术转换符的性质,i+j表达式的结果就是无符号类型的。但是题目中是 %d 的方式来打印,说明是有符号的方式来看待,那么最高位1就是符号位,转变为原码结果是:10000000 00000000 00000000 00001010(-10)
答案验证:
在这里插入图片描述
4.

//输出什么?
int main()
{
	unsigned char i = 0;
	for (i = 0; i <= 255; i++)
	{
		printf("hello world\n");
	}
	return 0;
}

这道题要注意 i 是无符号char类型的,所以i 的范围是0 ~ 255,i不可能超过255,即循环一直持续下去,就是死循环。

5.

#include <stdio.h>
#include <string.h>
int main()
{
	char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
	}
	printf("%d", strlen(a));
	return 0;
}

这道题我们先从数组入手,我们发现这是一个char类型的数组,而循环的内容就是向数组里面赋值,赋值1000次后要我们求字符数组的长度。我们知道strlen() 函数是一直在字符串里面统计字符个数,直到遇见 ‘\0’ 才停止,而循环里面不断的放 -1 -2 -3 -4·····
从表面看是这样的,但是不要忘记char类型的范围是 -128 ~ 127。所以当赋值到 -128时,下一个赋值的不是 -129,而是 127,接着是 126 125 124 ······直到0,然后又 -1
-2 -3·····地放进去,不断循环,直到放1000次后才结束。
所以strlen函数要找的 ’\0’ 就是ASCII为0,就是找到0就结束统计了,因此统计的范围是
-1 到 -128 共128个数和127到1共127个数,128+127=255;所以答案是 255。
在这里插入图片描述
答案验证:
在这里插入图片描述

总结

1.像第一题和第二题我们要注意 把一个整数放进字符char类型是放不下的,肯定会发生整形截断,但是还要我们以整数的方式来打印时,我们要看看自己字符类型是有符号还是无符号类型。如果是有符号类型,那我们就以最高位为符号位来补充够32位,如果是无符号类型,那我们就在前面补充0,凑到32位就可以了。
2.要注意自己设定类型变量的范围,不然就会导致死循环。
在这里插入图片描述
欢迎各位在评论区探讨学习,如有疑问的地方,还望指出。
都看到这里了,点个赞呗。

  • 15
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值