简介
在每本有关 C/C++ 的教科书中,您都会找到如下所示的运算符优先级和关联性表:
http://en.cppreference.com/w/cpp/language/operator_precedence
StackOverflow 上的一个问题是这样问的:
以下函数按什么顺序执行:
f1() * f2() + f3();
f1() + f2() * f3();
引用上一张图表,我自信地回答说函数具有从左到右的关联性,因此在前面的陈述中,在这两种情况下都是这样评估的:
f1() -> f2() -> f3()
函数评估完成后,您可以像这样完成评估:
(a1 * a2) + a3
a1 + (a2 * a3)
令我惊讶的是,很多人告诉我我完全错了。决心证明他们错了,我决定转向 ANSI C11 标准。我再次惊讶地发现,很少有人提到运算符优先级和关联性。
问题
- 如果我认为函数总是从左到右求值的观点是错误的,那么表中提到函数优先级和关联性的真正含义是什么?
- 如果不是 ANSI,谁来定义运算符优先级和关联性?如果是ANSI做出定义,为什么很少提及运算符优先级和关联性?运算符优先级和关联性是从 ANSI C 标准推断出来的,还是在数学中定义的?
最佳答案
运算符优先级在适当的标准中定义。 C 和 C++ 的标准是 C 和 C++ 到底是什么的一个真正定义。所以如果你仔细观察,细节就在那里。事实上,细节在语言的语法中。例如,看一下 C++ 中 +
和 -
的语法产生规则(统称为 additive-expressions):
additive-expression:
multiplicative-expression
additive-expression + multiplicative-expression
additive-expression - multiplicative-expression
如您所见,乘法表达式是加法表达式的子规则。这意味着如果你有类似 x + y * z
的东西,y * z
表达式是 x + y * z
的子表达式。这定义了这两个运算符之间的优先级。
我们还可以看到 additive-expression 的左操作数扩展为另一个 additive-expression,这意味着 x + y + z
, x + y
是它的子表达式。这定义了关联性。
关联性决定了如何对同一运算符的相邻使用进行分组。例如,+
是从左到右关联的,这意味着 x + y + z
将像这样分组:(x + y) + z
.
不要将其误认为是评估顺序。绝对没有理由不能在 x + y
之前计算出 z
的值。重要的是计算的是 x + y
而不是 y + z
。
对于函数调用运算符,从左到右的关联性意味着 f()()
(例如,如果 f
返回函数指针,则可能发生这种情况)像这样分组:(f())()
(当然,另一个方向没有任何意义)。
现在让我们考虑一下您正在查看的示例:
f1() + f2() * f3()
*
运算符的优先级高于 +
运算符,因此表达式按如下方式分组:
f1() + (f2() * f3())
我们甚至不必在这里考虑关联性,因为我们没有任何相同的运算符彼此相邻。
然而,函数调用表达式的求值是完全无序的。没有理由不能先调用 f3
,然后调用 f1
,然后调用 f2
。在这种情况下,唯一的要求是运算符的操作数在运算符之前进行评估。所以这意味着必须在评估 *
之前调用 f2
和 f3
并且必须评估 *
并且必须在评估 +
之前调用 f1
。
但是,有些运算符确实会对其操作数的求值进行排序。例如,在 x || y
,x
总是在 y
之前计算。这允许短路,如果 x
已知为 true
,则不需要评估 y
。
之前在 C 和 C++ 中使用 sequence points 定义了求值顺序,并且两者都更改了术语以根据 sequenced before 关系来定义事物.如需更多信息,请参阅 Undefined Behaviour and Sequence Points .
关于c++ - 谁定义了 C 运算符优先级和关联性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20767745/