为什么复合赋值 (+=) 因语言(Java、C++)而异?

Why does compound assignment (+=) differ between languages (Java, C++)?

+= 的定义在 Java 和 C++ 中似乎是相同的,但是,它们的表现不同。

考虑以下 C++ 代码:

#include <iostream>

int n;
int f(int x) {
    n += x;
    return x;
}
int main() {
    n = 0;
    n = n + f(3);
    std::cout<<n<<" ";
    n = 0;
    n += f(3);
    std::cout<<n<<" ";
    n = 0;
    n = f(3) + n;
    std::cout<<n<<std::endl;
}

这输出:3 6 6

Java中的类似代码输出:3 3 6,这里是参考代码。

static int n;
public static void main(String[] args) {
    n = 0;
    n = n + f(3);
    System.out.println(n);
    n = 0;
    n += f(3);
    System.out.println(n);
    n = 0;
    n = f(3) + n;
    System.out.println(n);
}
public static int f(int x) {
    n += x;
    return x;
}

查看 C++ and Java 的文档,他们写了类似的定义:

C++:

E1 op= E2 (where E1 is a modifiable lvalue expression and E2 is an rvalue expression or a braced-init-list (since C++11)) is exactly the same as the behavior of the expression E1 = E1 op E2, except that the expression E1 is evaluated only once and that it behaves as a single operation with respect to indeterminately-sequenced function calls

Java:

A compound assignment expression of the form E1 op= E2 is equivalent to E1 = (T) ((E1) op (E2)), where T is the type of E1, except that E1 is evaluated only once.

出于好奇,我在 Python 中检查了这个,它与 Java 的输出相同。当然,这样写代码是非常糟糕的做法,但我仍然很好奇解释。

我怀疑 += 在不同的语言中,变量的求值顺序是不同的,但我不知道具体是怎么回事。我在定义中遗漏了什么,复合赋值运算符是如何计算的?

评估顺序 - 严格 left-right in Java.

n += f(3);

所以:'n'是0。 f(3) returns 3.所以我们把0和3相加,把结果赋给n。 f() 中对 n 的赋值无效。

Java 语言规范:

[…] the value of the left-hand operand is saved and then the right-hand operand is evaluated. […]

对于 C++,我相信(但没有检查)求值顺序是未定义的。

在你的例子中,调用了 f(3),n 变成了 3,然后 f(3) 的结果被添加到 n 的新值中。


要判断一个表达式的含义,不能只看所涉及的运算符。求值顺序很重要,在同一个表达式中修改和使用变量的地方,细则很重要(结果可能会也可能不会在语言中定义)。

这与 求值顺序 的关系比“复合赋值运算符的作用”更多,因此您会在两种语言的规范。

对于Java,JLS §15.7

The left-hand operand of a binary operator appears to be fully evaluated before any part of the right-hand operand is evaluated.

If the operator is a compound-assignment operator (§15.26.2), then evaluation of the left-hand operand includes both remembering the variable that the left-hand operand denotes and fetching and saving that variable's value for use in the implied binary operation.

所以 += 左边的 n 先求值,到 0。然后右侧的计算结果为 3。然后将此值与左侧的总和写入 n.

对于 C++,Evaluation Order:

查看“规则”部分中的第 20 项:

In every simple assignment expression E1=E2 and every compound assignment expression E1@=E2, every value computation and side-effect of E2 is sequenced before every value computation and side effect of E1

在这里,首先评估 E2(右侧),直到 3,然后评估左侧。此时,n 已被 f 更改为 3,因此左侧的计算结果也为 3。