前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >表达式求值过程中会发生哪些隐藏的变化?求值顺序又由什么决定?——详解C表达式求值中的隐式类型转换,算术转换问题,以及操作符的属性

表达式求值过程中会发生哪些隐藏的变化?求值顺序又由什么决定?——详解C表达式求值中的隐式类型转换,算术转换问题,以及操作符的属性

作者头像
YIN_尹
发布2024-01-23 15:38:34
710
发布2024-01-23 15:38:34
举报
文章被收录于专栏:YIN_尹的博客YIN_尹的博客

我们写出的表达式,在求值的过程中,一定是按照我们所想的在一步一步运算吗?会不会发生一些我们察觉不到的变化呢?任意给定一个表达式,它的计算路径一定是确定的吗?

表达式求值

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

一.隐式类型转换——整型提升
1.什么是整型提升呢?

C的整型算术运算总是至少以缺省整型类型的精度来进行的。 为了获得这个精度,表达式中的字符和短整型(所占空间大小小于一个整型的大小)操作数在使用之前被转换为普通整型,这种转换称为整型提升。

比如:

char a,b,c; … a = b + c;

这里就会发生整型提升:

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

2.那如何进行整型提升呢?按照什么规则?

整形提升是按照变量的数据类型的符号位来提升的:

(1)负数的整形提升(高位补符号位1)

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

(2)正数的整形提升(高位补符号位0)

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

(3)无符号整形提升,高位补0

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

3.实战演练

好,了解了规则之后,我们就来举几个例子吧。 实例一:

代码语言:javascript
复制
#include <stdio.h>

int main()
{
	unsigned char a = -1;
	short b = 1;
	printf("%d", a + b);
	return 0;
}

大家思考思考,会不会发生整型提升,结果是啥,是-1+1=0吗? 好,我们一起来分析一下:

在这里插入图片描述
在这里插入图片描述

根据我们的分析答案是256。我看运行看看是不是:

在这里插入图片描述
在这里插入图片描述

实例二:

代码语言:javascript
复制
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;
}

这道题的结果又是啥呢?

在这里插入图片描述
在这里插入图片描述

看看结果:

在这里插入图片描述
在这里插入图片描述

只打印了c。

实例三:

代码语言:javascript
复制
int main()
{
 char c = 1;
 printf("%u\n", sizeof(c));
 printf("%u\n", sizeof(+c));
 printf("%u\n", sizeof(-c));
 return 0; }

我们分析结果应该是 1,4,4; c只要参与表达式运算,就会发生整形提升,表达式 +c ,发生整型提升,所以 sizeof(+c) 是4个字节(一个整型大小). 同理 sizeof(-c) 也是4个字节, 但是 sizeof( c ) ,就是1个字节,因为没有发生整型提升,还是char类型。

在这里插入图片描述
在这里插入图片描述
二.算术转换
1.什么是算术转换

然后我们来学习算术转换,那什么是算术转换呢?

算术转换: 如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。

下面的层次体系称为寻常算术转换。

在这里插入图片描述
在这里插入图片描述
2.举例
代码语言:javascript
复制
#include <stdio.h>
int main()
{
	int a = 10;
	double b = 22.2;
	printf("%d", sizeof(a + b));
	return 0;
}

这段代码会不会发生算术转换,结果是什么呢? 我们来分析一下:

在这里插入图片描述
在这里插入图片描述

我们看看结果:

在这里插入图片描述
在这里插入图片描述
三.操作符的属性
1.操作符如何控制表达式求值

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

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

那它们如何取影响表达式的求值顺序呢? (1)两个相邻的操作符先执行哪个?取决于他们的优先级。 (2)如果两者的优先级相同,取决于他们的结合性。

在这里插入图片描述
在这里插入图片描述

注意:N/A是空的意思,R/L是从右向左的意思,L/R是从左向右的意思。

举例说明一下:

在这里插入图片描述
在这里插入图片描述

如果优先级相同:

在这里插入图片描述
在这里插入图片描述

然后给大家解释一下控制求值顺序是什么意思吧!

举个例子,我们看到上面表格中的 rexp1? rexp2:rexp3 是控制求值顺序的。

其实就是rexp1可以决定rexp2,rexp3,哪一个表达式先算,哪一个后算. 因为我们知道他的运算规律是: 如果rexp1成立,执行rexp2,整个表达式的结果是rexp2的结果; 如果rexp1不成立,执行rexp3,整个表达式的结果是rexp3的结果

所以说它可以控制求值顺序

2.问题表达式

接下来我们一起来看一些表达式,大家判断一下它们的求值顺序:

代码语言:javascript
复制
a*b + c*d + e*f

这其实是一个问题表达式,为啥呢?

在计算的时候,由于“ * ”比+的优先级高,只能保证 “ * ”的计算是比+早,但是优先级并不能决定第三个*比第一个+早执行。

所以表达式的计算机顺序就可能是

ab cd ab + cd ef ab + cd + ef

或者

ab cd ef ab + cd ab + cd + ef

再来看一个:

代码语言:javascript
复制
c + --c;

同样存在一些问题: 同上,操作符的优先级只能决定自减- -的运算在+的运算的前面,但是我们并没有办法得知,+操作符的左操作数的值的获取是在右操作数- -之前获取的还是之后获取,所以结果是不可预测的,是有歧义的。

比如:

在这里插入图片描述
在这里插入图片描述

在不同的编译器上,结果就可能不一致!!!

再来一个:

代码语言:javascript
复制
int main()
{
 int i = 10;
 i = i-- - --i * ( i = -3 ) * i++ + ++i;
 printf("i = %d\n", i);
 return 0; }

代码3在不同编译器中的测试结果:

值 编译器 —128 Tandy 6000 Xenix 3.2 —95 Think C 5.02(Macintosh) —86 IBM PowerPC AIX 3.2.5 —85 Sun Sparc cc(K&C编译器) —63 gcc,HP_UX 9.0,Power C 2.0.0 4 Sun Sparc acc(K&C编译器) 21 Turbo C/C++ 4.5 22 FreeBSD 2.1 R 30 Dec Alpha OSF1 2.0 36 Dec VAX/VMS 42 Microsoft C 5.1

代码语言:javascript
复制
#include <stdio.h>
int main()
{
 int i = 1;
 int ret = (++i) + (++i) + (++i);
 printf("%d\n", ret);
 printf("%d\n", i);
 return 0; }

这段代码中的第一个 + 在执行的时候,第三个++是否执行,这个是不确定的,因为依靠操作符的优先级和结合性是无法决定第一个 + 和第三个前置 ++ 的先后顺序。 尝试在linux 环境gcc编译器,VS环境下都执行,看结果。

在这里插入图片描述
在这里插入图片描述

vs环境下:

在这里插入图片描述
在这里插入图片描述

这些都是有问题的表达式,我们在写代码的过程中,要避免写这样的代码!!!

3.总结

上面的问题表达式告诉我们:

即使我们知道了所有操作符的优先级和结合性,以及其是否控制求值顺序,我们也不能保证任意写一个表达式,它的求值顺序就一定是确定的,我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。

所以,对于比较复杂的代码我们可以拆开写,养成良好的代码风格,避免写出这样不好的代码!!!

以上就是对表达式求值的一些问题讲解,欢迎大家指正!!!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-08-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 表达式求值
    • 一.隐式类型转换——整型提升
      • 1.什么是整型提升呢?
      • 2.那如何进行整型提升呢?按照什么规则?
      • 3.实战演练
    • 二.算术转换
      • 1.什么是算术转换
      • 2.举例
    • 三.操作符的属性
      • 1.操作符如何控制表达式求值
      • 2.问题表达式
      • 3.总结
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档