Operator Precedence vs Order of Evaluation (运算符优先级与求值顺序) In programming, operators have different levels of precedence, meaning some operators are evaluated before others. This affects the order in which expressions are evaluated in an expression stateme
Operator Precedence vs Order of Evaluation (运算符优先级与求值顺序) In programming, operators have different levels of precedence, meaning some operators are evaluated before others. This affects the order in which expressions are evaluated in an expression stateme
\"运算符优先级\"和\"求值顺序\"是编程中常用的术语,对于程序员来说非常重要。就我理解而言,这两个概念是紧密相关的;在讨论表达式时不能缺少其中任何一个。\n让我们看一个简单的例子:\n
int a=1; // 第1行 a = a++ + ++a; // 第2行 printf("%d",a); // 第3行
\n现在很明显,第2行会导致未定义的行为,因为在 C 和 C++ 中的\"序列点\"包括:\n
\n
\n
- 在逻辑与(&&)、逻辑或(||)和逗号运算符的左右操作数的求值之间。例如,在表达式
*p++ != 0 && *q++ != 0
中,子表达式*p++ != 0
的所有副作用在访问q
之前完成。\n
- 在条件运算符(三目运算符)的第一个操作数和第二个或第三个操作数之间的求值。例如,在表达式
a = (*p++) ? (*p++) : 0
中,第一个*p++
之后有一个序列点,这意味着第二次执行之前它已经被增加。\n
- 在完整表达式的末尾。这个类别包括表达式语句(如赋值
a=b;
)、返回语句、if、switch、while 或 do-while 语句的控制表达式,以及 for 语句中的所有三个表达式。\n
- 在函数调用中进入函数之前。参数的求值顺序没有指定,但这个序列点意味着在进入函数之前它们的所有副作用都已经完成。在表达式
f(i++) + g(j++) + h(k++)
中,f
以i
的原始值作为参数调用,但是在进入f
的主体之前,i
已经被递增。同样,j
和k
在进入g
和h
之前进行更新。然而,并没有指定f()
、g()
、h()
的执行顺序,也没有指定i
、j
、k
的递增顺序。因此,在f
的主体中j
和k
的值是未定义的。3 注意,函数调用f(a,b,c)
不是逗号运算符的使用,因此对于a
、b
和c
的求值顺序是未指定的。\n
- 在函数返回之后,将返回值复制到调用上下文中。(这个序列点只在 C++ 标准中指定;在 C 中只是隐含存在。)
\n
- 在初始化器的末尾;例如,在声明
int a = 5;
中,5的求值之后。\n
\n
\n因此,根据第3点:\n在完整表达式的末尾。这个类别包括表达式语句(如赋值a=b;
)、返回语句、if、switch、while 或 do-while 语句的控制表达式,以及 for 语句中的所有三个表达式。\n第2行明显会导致未定义的行为。这展示了\"未定义行为\"与\"序列点\"密切相关的关系。\n现在我们再看另一个例子:\n
int x=10,y=1,z=2; // 第4行 int result = x\n现在很明显,第5行会使变量
result
存储1
。\n现在,表达式x
在第5行可以解释为 x<(y
或 (x
。在第一种情况下, result
的值将是0
,在第二种情况下,result
将是1
。但我们知道,当\"运算符优先级\"相同时,\"结合性\"就会起作用,因此,它被解释为(x
。\n这就是这篇MSDN文章中所说的:\nC 运算符的优先级和结合性影响表达式中操作数的分组和求值。只有当其他优先级更高或更低的运算符存在时,运算符的优先级才具有意义。具有更高优先级的运算符的表达式先求值。优先级也可以用\"绑定\"一词来描述。具有更高优先级的运算符被称为\"结合更紧\"。\n关于上述文章:\n它提到\"具有更高优先级的运算符的表达式先求值\"。\n这听起来可能是不正确的。但是,如果我们考虑到 ()
也是一个运算符,x
就等同于 (x
,我认为这篇文章并没有说错什么。我的理由是如果不考虑结合性,那么完整表达式的求值将变得模糊,因为 <
不是一个序列点。\n我还找到了另一个链接,在运算符优先级和结合性中提到:\n此页面按照优先级顺序列出了 C 运算符(从高到低)。它们的结合性指示在表达式中具有相等优先级的运算符的应用顺序。\n因此,以int result=x
的第二个例子为例,我们可以看到这里有3个表达式, x
、y
和z
,因为最简单的表达式是由单个字面常量或对象组成的。因此,表达式x
、y
、z
的结果将是它们的rvalues,即10
、1
和2
。因此,我们可以将x
解释为 10<1<2
。\n现在,结合性会起作用,因为现在我们有2个要求值的表达式,要么是10<1
,要么是1<2
,并且由于运算符的优先级相同,它们从左到右进行求值。\n以我提出的最后一个例子作为论据:\nint myval = ( printf("Operator\n"), printf("Precedence\n"), printf("vs\n"), printf("Order of Evaluation\n") );\n在上面的例子中,由于逗号运算符具有相同的优先级,表达式从
左到右
进行求值,并将最后一个printf()
的返回值存储在myval
中。\n在SO/IEC 9899:201x的J.1 未指定行为中提到:\n子表达式的求值顺序和副作用发生的顺序,除了函数调用、&&、||、?: 和逗号运算符(6.5)规定的情况外,都是未指定的。\n现在我想知道,是否可以说:\n求值顺序取决于运算符的优先级,不考虑未指定行为的情况。\n如果我在问题中说错了什么,我希望能得到纠正。\n我提出这个问题的原因是因为 MSDN 文章在我脑海中造成了困惑。它是一个错误吗?
运算符的优先级和计算顺序是编程中常遇到的问题。为了理解这个问题,我们可以从表达式树的角度来看待它。
假设我们有一个表达式,比如x+y*z
,我们可以将其重写为表达式树:
按照优先级和结合性规则:
x + ( y * z )
应用完优先级和结合性规则后,我们可以安全地忽略它们。
树形式:
x + y * z
现在,这个表达式的叶子节点是x
、y
和z
。这意味着你可以按照任意顺序计算x
、y
和z
,也意味着你可以按照任意顺序计算*
和x
的结果。
现在,由于这些表达式没有副作用,所以你并不关心计算顺序。但是如果有副作用,计算顺序可能会改变结果,而且由于计算顺序可以是任意的,由编译器决定,这就是一个问题。
现在,序列点为这个混乱带来了一些秩序。它们将树分成了几个部分。
x + y * z, z = 10, x + y * z
优先级和结合性之后
x + ( y * z ) , z = 10, x + ( y * z)
树形式:
x + y * z , ------------ z = 10 , ------------ x + y * z
树的上部分将在中间部分之前被计算,中间部分将在底部之前被计算。
好吧,这篇文章基本上是正确的。或者更委婉地说,我知道作者的意思。对于MSVC而言,这可能完全正确(编译器可能会强加额外的顺序)。但是,优先级和结合性规则只定义了表达式树的形状,而不是计算顺序。
这取决于你如何看待它。首先,优先级定义了部分顺序。 x+y*z
意味着对于计算+
,你需要*
的结果。这是由运算符优先级定义的顺序。现在,计算顺序需要遵循这个部分顺序,但除此之外,它可以做任何它想做的事情。所以你不能真的说它们完全无关。但是它们也不是相互定义的。
_Me_Be: 部分顺序是指什么?首先,优先级定义了部分顺序。x+y*z意味着对于计算+,你需要*的结果。
这是完全错误的。运算符是不会被计算的,操作数才会被计算。
不,表达式会被计算。
_Me_Be: 是的!那你的观点是什么?表达式会被计算。操作数是表达式的一部分。我没看出有什么区别。你说的that for evaluating +
是什么意思?有人怎么可能计算+
?
不,操作数是独立的表达式。运算符和它的操作数形成另一个表达式。而你问的x + y * z
中有人怎么可能计算+
?通过先计算它的操作数,当它们被计算时,再计算表达式本身。
_Me_Be: 我的唯一观点是计算顺序和运算符的优先级是无关的。请看前ISO C委员会成员Peter Seebach在usenet上的详细讨论,他明确提到计算顺序和运算符的优先级是两个独立的事情。
抱歉,但你显然对他的意思有困惑。他谈论的是表达式树的叶子上的表达式的顺序。
_Me_Be: 我对他的意思没有困惑。他谈论的是表达式树叶子上表达式的计算顺序,而且这个顺序与运算符的优先级无关。实际上,这个顺序是未指定的。
_Me_Be: 还请看一下Jerry Coffin的回答。
: 我已经读过了。我仍然相信Jerry Coffin的回答是100%正确的。:)
是的,但你不是,这就是问题所在。
_Me_Be: 只有一个问题,那就是你对我的理解有问题。:-)
"计算顺序"只是指表达式树的叶子上的顺序。部分顺序仍然存在,并且由表达式树的形状定义。这就是你的误解之处。你坚持声称没有任何顺序。
运算符优先级是指在一个表达式中,不同运算符的优先级不同,决定了它们被执行的顺序。而运算符的优先级与运算对象的求值顺序是无关的。求值顺序是指在一个表达式中,不同运算对象的求值顺序。本文主要讨论了这两者的区别以及可能产生的问题。
首先,运算符优先级和结合性是用来控制运算对象的求值顺序的,而不是决定运算对象的求值顺序。以表达式"x 同样地,即使改变了运算符的优先级,求值顺序也可以保持不变。将表达式"x*y+z"的优先级改变一下,仍然可以先求值"z",然后再求值"x"和"y",最后进行乘法和加法运算。这表明运算符的优先级并不决定运算对象的求值顺序。 即使在存在副作用的情况下,这一点依然成立。可以将副作用看作是在下一个序列点(例如表达式的末尾)完成的操作。对于类似"a=b++ + ++c;"的表达式,可以将其执行过程看作是先对"a"进行赋值,然后对"b"和"c"进行递增操作。这个例子说明了即使存在表达式目标的情况下,也是先对"a"进行求值,然后再对"b"和"c"进行求值。这个例子还说明了一个表达式中的顺序依赖关系并不会影响求值顺序。 当在下一个序列点之前使用值时,会导致未定义行为。这是因为在这段时间内,"另一个线程"(可能)正在修改该数据,而你没有办法与另一个线程同步访问。因此,在使用一个经过前置/后置递增/递减操作的变量时,会导致完全未定义的行为。 总结起来,求值顺序与运算符的优先级、结合性以及明显的依赖关系是无关的。试图依赖求值顺序来实现某些操作是不可靠的,因为你无法保证你所确定的任何部分都是正确的。依赖求值顺序的代码容易出错,可能得到完全不同的结果。因此,在编写代码时,应尽量避免依赖求值顺序来实现逻辑。