表达式求值(下):问题表达式

        哈喽大家好,本期内容为特别篇“表达式求值”的下半部分:问题表达式,内容相较于上篇要简单的多,关于隐式类型转换“整型提升”的内容不知道大家理解了几成呢,其实关于整型提升的例题还有很多,我也做了挺多的,不过感觉也都是大同小异,中心思想就是提升时的补位,然后注意负数的值要补码变原码求值,细心一点并不难做。重点还是对于整型提升的理解,大家把最后总结的三点理解就好啦!

        那么我们开始本期内容,“问题表达式”什么意思呢?顾名思义嘛,就是有问题的表达式,学习本期内容就是告诉大家以后不要写出这种表达式来,他们的特点就是不报错但是在不同编译器执行路径不同,甚至有时候结果不同。直接上例题:

int main()
{
	int a, b, c;
	a = 1;
	b = 2;
	c = 3;
	a = a*b + b*c +a*c;
	printf("%d\n", a);
	return 0;
}

        这段代码相信很多小伙伴看到都会觉得,这很对啊,没什么问题呀,因为我一开始也是这样想的哈哈。此代码就是典型的日常问题代码之一,原因在于a = a*b + b*c +a*c的计算路径不唯一。什么意思呢,因为如果编译器直接编译整个式子,按照优先级计算,先算乘除再算加减,那么就有a = 2 + 6 + 3 =  11,因为我们计算就是这样算的,而实际上有的编译器是从左向右编译,编译到到“a*b+”时先算乘法为“2+2*3”然后为“2+6+a*c”,这时下一步运算比较优先级时两个都为“+”,所以得“8+a*c”得“8+3=11”,虽然结果一样,但是我们可以发现它计算路径是不唯一的,也就说这个代码在不同编译器下路径不唯一,在一个平台写的代码换平台就可能出现bug。对于不同编译器效果不同多说一句,上期内容的sizeof求!a的值有的就是整型提升的4,当然大多数编译器都是将其作为bool值计算为1。

接下来看第二段代码:

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

        当编译器执行“b = a + --a”的时候,第一种情况就是先全部编译,根据优先级,先算“--a”,此时a已经等于0,在计算加法得到“0+0=0”。而第二种则是从左向右编译计算,编译到“a + 一个值”时,发现右值有“--”运算,该优先级高于“+”,所以算完“--a”,再继续编译计算,得到结果1+0=1。这样这个式子的值在不同编译器下就是不可预测值(我用的Vs2019结果为0),也是一种典型的问题代码。

        好,接下来我们来看点变态的,我估计正常人都不会这么写代码哈,但是没办法,考试是可能考到的,让我们来看例题3:

int main()
{
	int i = 10;
	i = i-- - --i * (i = -3) * i++ + ++i;
	printf("%d\n", i);
	return 0;
}

        这个题估计大家一眼就感觉是问题代码吧,要是自己以后工作了接盘程序里有这样的代码肯定还要亲切问候写这个代码的家人一下呀!好了,不开玩笑,这样的代码可是写不得的啊,歧义太过严重,其实原因还是上面讲的,不同的编译器在处理的时候,虽然操作符优先级都是一样的规范,但是这个值在读取计算时是算完哪一步读取的值是没有规定的,就像--i,这里的i根本无法确定是哪一步计算完获取的i进行--运算,还是说直接所有的++,--都用10计算,还是都用-3计算,又或者算一步读一次值。这样的表达式别说是我们来计算,即使是计算机也会犯迷糊而不知如何计算。那么这个式子离谱到什么程度呢,那就是在n多编译器下有相当多种不同的结果,如下图(我的编译器结果为4):

让我们继续来看例题4:

int fun()
{
	static int count = 1;
	return ++count;
}

int main()
{
	int a = fun() + fun() * fun();
	printf("%d\n", a);
	return 0;
}

        那么这个代码的问题呢还是计算路径,只是把变量换成了函数,这个代码中的计算,无法确定是先调用函数再按优先级计算,结果为2+3*4=14;还是说按优先级进行调用,结果为4+2*3=10。在我的编译器上结果为14。所以综上来看,即使是在我的这个相同的编译环境下,这些问题的表达式的计算路径规律都是不同的,有的先取值再按优先级计算,有的边计算边更新变量或者函数的值,所以这些表达式是有问题的,是问题表达式。

        包括下面我们在来看一个例题5,即使是在使用“()”的情况下依旧是编译有不同的顺序,因为我们的运算符有优先级,但是取值时机仍然不确定:

int main()
{
	int i = 1;
	int a = (++i) + (++i) + (++i);
	printf("%d\n", a);
	return 0;
}

        在我的Windows操作系统下的VS编译器中,运算为先算三个++得到结果为i=4,然后三个i相加结果为12。而在Linux系统下,计算为从左向右编译,a=2+3+4=10。

小结

        我们在自己写代码的时候,一定要写出只有一种运算路径的代码,只要运算路径不唯一,那就是问题表达式,代码也就是有问题的错误的代码。形如例题5,我们完全可以写为“a = ++i;b = ++i;c = ++i;d = a + b + c;”,这样无论在什么编译器下,结果都毫无疑义,因为顺序是惟一的,结果为2+3+4=10。

        这期内容呢就是两点,第一自己别写问题表达式,复杂的式子尽量拆开写,第二就是试题中或者面试见到这样的式子要知道它是问题表达式,是错误的。好啦,本期的内容就到这里啦,如果觉得这篇文章对你有帮助欢迎收藏点赞转发,如果发现问题或者有不解之处也欢迎小伙伴们在评论区交流哦。最后喊出我们的口号“关注小白阿g,让小白不再白学!”亲爱的小伙伴们下期见。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值