0

的定义+=在 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++Java的文档,他们编写了类似的定义:

C++:

E1 op= E2(其中 E1 是可修改的左值表达式,E2 是右值表达式或花括号初始化列表(C++11 起))与表达式 E1 = E1 op E2 的行为完全相同,除了表达式 E1 只计算一次,并且对于不确定顺序的函数调用,它表现为单个操作

爪哇:

E1 op= E2 形式的复合赋值表达式等价于 E1 = (T) ((E1) op (E2)),其中 T 是 E1 的类型,除了 E1 只计算一次。

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

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

4

2 回答 2

3

这与评估顺序有关,而不是“复合赋值运算符的作用”,因此您会在两种语言规范的“评估顺序”部分中找到更多有用的东西。

对于 Java,JLS §15.7

在评估右侧操作数的任何部分之前,二元运算符的左侧操作数似乎已被完全评估。

如果运算符是复合赋值运算符(第 15.26.2 节),则左侧操作数的评估包括记住左侧操作数表示的变量以及获取和保存该变量的值以用于隐含的二元运算.

所以n在左边+=首先评估,到0。然后右手边计算为3。然后将该值与左侧的总和写入n

对于 C++,评估顺序

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

在每个简单赋值表达式 E1=E2 和每个复合赋值表达式 E1@=E2 中,E2 的每个值计算和副作用都排在 E1 的每个值计算和副作用之前

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

于 2020-08-20T03:31:35.770 回答
2

评估顺序 - 在 Java 中严格从左到右。

n += f(3);

所以:'n' 是 0。f(3) 返回 3。所以我们将 0 和 3 相加,并将结果分配给 n。在 f() 中对 n 的赋值无效。

Java语言规范:

[…] 左侧操作数的值被保存,然后评估右侧操作数。[…]

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

在您的情况下,调用了 f(3),n 变为 3,然后将 f(3) 的结果添加到 n 的新值中。


要确定表达式的含义,不能只看涉及的运算符。评估顺序很重要,并且在同一表达式中修改和使用变量时,细则很重要(结果可能在语言中定义,也可能不定义)。

于 2020-08-20T03:17:47.527 回答