微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

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

在这里插入图片描述

这篇文章,将带领大家深入的讨论和学习这些问题,一起来看看吧!

表达式求值

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

一.隐式类型转换——整型提升

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.实战演练

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

#include <stdio.h>

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

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

在这里插入图片描述

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

在这里插入图片描述


实例二:

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。

实例三:

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.举例

#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.问题表达式

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

a*b + c*d + e*f

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

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

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

ab
c
d
ab + cd
ef
a
b + cd + ef

或者

ab
c
d
ef
a
b + cd
a
b + cd + ef

再来看一个

c + --c;

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

比如:

在这里插入图片描述

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

再来一个

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

#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.总结

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

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

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

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

在这里插入图片描述

原文地址:https://www.jb51.cc/wenti/3284481.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐