Java 如果括号具有更高的优先级,那么为什么要先求解递增运算符?

Java 如果括号具有更高的优先级,那么为什么要先求解递增运算符?,java,operator-precedence,Java,Operator Precedence,我有一行代码 int a = 10; a = ++a * ( ++a + 5); 我的预期输出是12*(11+5)=192,但我得到了187。据我所知,首先要解决()内部的增量运算符,然后为什么首先解决外部的增量运算符?表达式从左到右求值。括号(和优先级)只表示分组,不表示求值顺序 所以 相等于 187 引述自: 算术表达式的计算由三组规则控制:优先级规则、关联性规则和顺序规则 优先级规则描述了当表达式混合不同类型的运算符时,应如何将底层表达式括起来 关联性规则描述了当一个底层表达式有一组相

我有一行代码

int a = 10;
a = ++a * ( ++a + 5);

我的预期输出是
12*(11+5)=192
,但我得到了187。据我所知,首先要解决
()
内部的增量运算符,然后为什么首先解决外部的增量运算符?

表达式从左到右求值。括号(和优先级)只表示分组,不表示求值顺序

所以

相等于

187
引述自:

算术表达式的计算由三组规则控制:优先级规则、关联性规则和顺序规则

优先级规则描述了当表达式混合不同类型的运算符时,应如何将底层表达式括起来

关联性规则描述了当一个底层表达式有一组相同类型的运算符时,该表达式应如何用括号括起来

求值顺序规则描述表达式中每个操作数的求值顺序

更高的优先级会导致操作数与运算符分组,并不意味着对操作数求值。计算顺序决定了表达式中子表达式的计算顺序


更新: 正如我所看到的,许多程序员都这么认为

a = ++a * ( ++a + 5);  
将调用未定义的行为。是,如果不能保证运算符操作数的求值顺序,它将调用UB

但这在java编程语言的上下文中是不正确的。它在java(以及C#)中具有定义良好的行为。Java语言规范规定:

Java编程语言保证运算符的操作数按特定的求值顺序(即从左到右)求值

示例15.7.1-1。首先计算左手操作数

在下面的程序中,
*
运算符的左侧操作数包含对变量的赋值,右侧操作数包含对同一变量的引用。引用产生的值将反映分配首先发生的事实

class Test1 {
    public static void main(String[] args) {
        int i = 2;
        int j = (i=3) * i;
        System.out.println(j);
    }
}
该程序产生以下输出:

9
不允许对
*
运算符的求值产生6而不是9

但是,java规范仍然明确指出,不要编写这样的代码:

建议代码不要严重依赖此规范。当每个表达式包含最多一个副作用(作为其最外层的操作)时,以及当代码不确切地依赖于表达式从左到右求值所产生的异常时,代码通常更清晰


括号的作用是控制哪些计算值用作后续操作的操作数。它们控制操作的执行顺序,操作只能在操作数完成后才能计算。考虑表达式:

(a()+b()) * (c()+d())
a() + (b()*c()) + d()
括号不必(在Java中也不能)影响调用a()、b()、c()和d()的顺序。它们可能会影响在调用d()之前还是之后执行乘法,但只有在非常罕见的情况下(例如,d()调用Java本机接口,以Java不知道的方式改变乘法中使用的数字舍入模式)代码是否能够知道或关心乘法是在d()之前还是之后执行的

否则,重要的是,在第一种情况下,一个加法操作将作用于a()和b(),另一个作用于c()和d();乘法将作用于a()+b()和c()+d()。在另一种情况下,第一次乘法将作用于b()和c(),第一次加法作用于a()和前面提到的乘积,第二次加法作用于第一次求和和和d()。

int a = 10;
a = ++a * ( ++a + 5);
有时,最简单的解决方案是使用javap来理解评估顺序:

 0: bipush 10 // push 10 onto the stack (stack={10})
 2: istore_1  // store 10 into variable 1 (a = 10, stack={})
 3: iinc 1, 1 // increment local variable 1 by 1 (a = 11, stack={})
 6: iload_1   // push integer in local variable 1 onto the stack (a = 11, stack = {11})
 7: iinc 1, 1 // increment local variable 1 by 1 (a = 12, stack={11})
 10: iload_1  // push integer in local variable 1 onto the stack (a = 12, stack = {12, 11})
 11: iconst_5 // load the int value 5 onto the stack (a = 12, stack = {5, 12, 11})
 12: iadd     // add 5 and 12 (a = 12, stack = {17, 11})
 13: imul     // multiply 17 and 11 (a = 12, stack = {})
  • a
    递增1。(第3行)//a=11
  • a
    递增1。(第7行)//a=12
  • 5
    添加到
    a
    (第12行)//a=17
  • 11
    乘以
    17
    (第13行)//a=187
  • (10+1+1+5)*11=187

      int a = 10;
      a = ++a * ( ++a + 5);
    
    上述类型的表达式总是以从左到右的方式进行计算,无论是C还是JAVA

    在这种情况下,它是这样解决的 11*(12+5),结果是11*17=187//w.r.t java

    但是如果我们用C语言来解同一个表达式

    然后答案会随着评估方式的改变而改变

    在c中,第一次预增量/预减量发生,因此如果“N”的次数为预inc/dec 表达式中是否有inc/dec将首先出现“N”次

    然后,在表达式中变量的每次出现时,将替换相同的值,并计算表达式值,然后进行后期递增/递减

    i、 当表达式中的a有两次递增时,EA会先递增到11,然后再递增到12,然后表达式的计算结果为


    12*(12+5)=12*17=204//w.r.t C-language

    需要明确的是,这应该是一个学术练习,而不是生产代码。无论任何特定语言是否具有定义良好的语义,这都是不清楚和令人困惑的,应该避免。使用那些序列点!括号中的全部内容首先被计算的想法是完全错误的,所以不要相信这一点。将表达式插入括号时,该运算符将首先运行。因此,如果你有
    A()*(B()+C())
    ,那么加法发生在乘法之前,但这并不意味着对
    B()
    的调用发生在对
    A()
    的调用之前。它发生在之后。命令
      int a = 10;
      a = ++a * ( ++a + 5);