表达式求值顺序简介

前提

表达式有两种功能:

  • 表达式一定会产生一个值
  • 表达式可能会产生“副作用”(side effect)
    • 比如,i++的返回值是i,副作用是导致i增加1。
    • 比如,++i的返回值是i+1,副作用也是导致i增加1。

表达式中的副作用何时生效以及运算对象的求值顺序,会影响到表达式的值。比如:

int a = 4, b;  
b = a++ + a;  
  • 如果“+”的两个运算对象,从左到右计算
    • 如果后缀递增运算符的副作用,在计算第二个操作数“a”时,已经生效,那么b的值是9
    • 如果后缀递增运算符的副作用,在进入到“+”运算之时,才生效,那么b的值是8
  • 如果“+”的两个运算对象,从右到左计算
    • b的值都是8

因此下面讲述两个概念:

  • 顺序点
  • 求值顺序

顺序点

在C、C++等允许表达式有副作用的语言中,通常都规定了顺序点,顺序点就是表达式中的副作用的最晚生效时刻。在程序执行中,存在一系列的顺序点,一旦执行到一个顺序点,此前的副作用都必须生效。但是在顺序点之间并没有任何保证。
C/C++明确定义了顺序点的概念。顺序点位于:

  • 每个完整表达式结束时。完整表达式包括变量初始化表达式,表达式语句,return语句的表达式,以及条件、循环和switch语句的控制表达式(for头部有三个控制表达式)
  • 运算符 &&、||、?: 和逗号运算符的第一个运算对象计算之后
  • 函数调用中对所有实际参数和函数名表达式(需要调用的函数也可能通过表达式描述)的求值完成之后(进入函数体之前)

假设时刻ti和ti+1是前后相继的两个顺序点,到了ti+1,必须实现ti之后发生的所有副作用。当然也可以不等到时刻ti+1,完全可以选择在时段 [ti, ti+1] 之间的任何时刻实现在此期间出现的副作用。


求值顺序

(a + b) * (c + d)
“*”的两个运算对象哪个先算?
fun(a++, b, a+5)
fun及其三个参数按什么顺序计算?
对第一个表达式,采用任何计算顺序都没关系,因为其中的子表达式都是引用透明的。第二个例子里的实参表达式出现了副作用,计算顺序就非常重要了。在计算第二个表达式时,首先按照某种顺序算fun、a++、b和a+5,之后是顺序点,而后进入函数执行。
还要注意顺序点的问题:即使某一边的表达式先算了,其副作用也可能没有反映到内存,因此对另一边的计算没有影响。
Java明确规定求值顺序是从左到右。

感谢浏览tim chow的作品!

如果您喜欢,可以分享到: 更多

如果您有任何疑问或想要与tim chow进行交流

可点此给tim chow发信

如有问题,也可在下面留言: