数学表达式的正确优先级是什么?

14

在Java中,这个表达式中数学运算的正确顺序是什么:

    a + b  * c / ( d - e )
1.    4    1   3     2
2.    4    2   3     1

我知道这两个答案的结果是相同的。但我希望完全了解java编译器的逻辑。在这个例子中是先执行乘法还是括号里的表达式?提供一个覆盖该问题的文档链接将会有帮助。

更新:谢谢大家的回答。你们大部分人写道,首先会评估括号中的表达式。在查看Grodriguez提供的参考资料后,我创建了一些小测试:

int i = 2;
System.out.println(i * (i=3)); // prints '6'
int j = 2;
System.out.println((j=3) * j); // prints '9'

能否有人解释一下为什么这些测试会产生不同的结果?如果括号中的表达式先被计算,我会期望得到相同的结果-9。


1
这些回答都没有回答他的问题,因为他问的是括号;它们不包含在优先级文档中。值得注意的是,最好这样问:括号表达式首先被评估还是在需要时评估?如果首先评估,那么(2)将是正确的...如果进行惰性评估,则(1)将是正确的。我不知道答案,但也许这对你有所帮助。 - Myrddin Emrys
“先评估左操作数”规则。请参阅我的编辑答案。 - Edgar Bonet
@bancer:任何二元运算符的左操作数在右操作数之前被完全计算。这解释了你的第二个问题。我已经更新了我的答案以反映这一点。 - Grodriguez
@Grodriguez - 我不知道Java,但在C中,括号会根据需要进行评估,这是“短路”优化的关键点。如果您的评论是一般性的,则不正确。 - ysap
1
一个括号,两个括号。 - Andreas Rejbrand
显示剩余2条评论
8个回答

13

正如JeremyP所漂亮地展示的那样,第一个答案是正确的。

一般来说,以下规则适用:

  • 在执行操作本身之前,每个运算符的操作数都会被求值(除了||&&?:)。
  • 操作数从左到右求值。二元操作符的左操作数似乎在右操作数的任何部分被求值之前就已经完全求值了。
  • 求值顺序遵循括号和运算符优先级:
    • 首先计算括号。
    • 按照优先级顺序计算运算符。
    • 具有相同优先级的运算符从左到右求值,但赋值运算符从右到左求值。

请注意,前两条规则解释了您第二个问题的结果:

int i = 2;
System.out.println(i * (i=3)); // prints '6'
int j = 2;
System.out.println((j=3) * j); // prints '9'

参考文档:

http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#4779

教程:

http://download.oracle.com/javase/tutorial/java/nutsandbolts/operators.html


这个描述在表达式生成的结果方面是正确的,但并不总是正确地说“括号首先被评估”。如果括号中的任何内容都是方法,则可能会有所不同。 - DJClayworth
1
第二个答案不正确。*/具有相等的优先级且左结合。因此,该表达式相当于a + ((b * c) / (d - e))。加上左操作数先评估的规则,意味着首先评估b * c - JeremyP
二元运算符的左操作数似乎在右操作数的任何部分被评估之前完全被评估。我认为这意味着如果括号在操作数的右侧,则它们将在左侧的任何内容被评估后进行评估。这肯定可以解释Bancer在他的更新中观察到的情况。不过,我觉得没必要添加一个新答案来说明这一点,所以我想评论一下,让你更新你的答案。 - Chris
1
@Grodriguez:不会的。乘法会在减法之前进行计算,因为除法的左操作数会先于右操作数进行计算。 - JeremyP
@Grodriguez:请看一下我在下面答案中添加的测试程序。它表明除法的左侧在右侧之前被评估。 - JeremyP
@Grodriguez:我取消了我的投票并点赞,因为你是第一个链接到JLS的人。 - JeremyP

13

迄今为止,几乎每个人都将求值顺序与运算符优先级混淆。在Java中,优先级规则使表达式等效于以下内容:

a + (b  * c) / ( d - e )

因为*/的优先级相等且是从左往右结合的.

除了||和&&运算符外,表达式的求值顺序严格按照左操作数先于右操作数,然后是运算符。所以求值顺序为:

  a
      b
      c
    *
      d
      e
    -
  /
+

求值的顺序沿着页面往下进行。缩进反映了语法树的结构。

编辑

针对Grodriguez的评论。以下程序:

public class Precedence 
{
    private static int a()
    {
        System.out.println("a");
        return 1;
    }   
    private static int b()
    {
        System.out.println("b");
        return 2;
    }
    private static int c()
    {
        System.out.println("c");
        return 3;
    }
    private static int d()
    {
        System.out.println("d");
        return 4;
    }
    private static int e()
    {
        System.out.println("e");
        return 5;
    }

    public static void main(String[] args) 
    {
        int x = a() + b() * c() / (d() - e());
        System.out.println(x);
    } 
}

生成输出结果

a
b
c
d
e
-5

这明显显示了减法之前进行了乘法运算。


1
Java编程语言的实现必须尊重由括号显式指定和由运算符优先级隐式指定的求值顺序。(JLS,15.7.3) - Grodriguez
@Grodriguez:在二元运算符的左操作数似乎完全被评估之前,右操作数的任何部分都不会被评估。(JLS 15.7.1)。[我的强调]。在我的例子中,评估顺序完全符合15.7.3。 - JeremyP
1
@JeremyP 哇,我写了完全相同的程序来证明我是正确的。 - ILMTitan
你是否有任何理由相信乘法的右操作数只是c而不是(c-(d-e))? 我总体上同意你的推理(并在其他地方展示了在C#中测试这些内容的答案),但我假设它本质上会对其右手操作数进行贪婪匹配...我正在尝试想出一种测试方法... - Chris
1
感谢您的澄清。这是一段有趣而愉快的旅程,让我得到了答案。也许比我预期的更分散了我的注意力。 :) - Chris
显示剩余4条评论

4
它按照以下顺序评估表达式。变量名称是需要评估的表达式。
a + b * c / (d - e)
    2   3    5   6
      4        7
1         8
  9

因此,您问题的答案是#1。操作顺序确定表达式树的形状(左侧树和右侧树),但始终首先评估左侧(根最后评估)。

1

我想它可能会从左到右评估类似这样的东西。

a+b*c/(d-e)

Action           Left Value      Right Value
Start Add        a               b*c/(d-e)
Start Multiply   b               c
Calc Multiply (since it can)    
Start Divide     b*c             (d-e)
Start Subtract   d               e
Calc Subtract
Calc Divide
Calc Add

可以将其视为创建一个表示计算的二叉树,然后从叶节点开始,从左到右进行计算。不幸的是,我的ASCII艺术并不好,但这里尝试表示所讨论的树:

   Add
    /\
   /  \
  a    \
     Divide
       / \
      /   \
     /     \
    /       \
Multiply  Subtract
  /\         /\
 /  \       /  \
b    c     d    e

我在C#中进行了一些测试(我知道它们不完全相同,但那是我的兴趣所在,并且测试可以很容易地进行调整)如下:

        f = 1;
        Console.WriteLine((f=2) + (f) * (f) / ((f) - (f)-1));
        Console.WriteLine(2 + 2 * 2 / (2 - 2 - 1));
        f = 1;
        Console.WriteLine((f) + (f=2) * (f) / ((f) - (f)-1));
        Console.WriteLine(1 + 2 * 2 / (2 - 2 - 1));
        f = 1;
        Console.WriteLine((f) + (f) * (f = 2) / ((f) - (f)-1));
        Console.WriteLine(1 + 1 * 2 / (2 - 2 - 1));
        f = 1;
        Console.WriteLine((f) + (f) * (f) / ((f=2) - (f)-1));
        Console.WriteLine(1 + 1 * 1 / (2 - 2 - 1));
        f = 1;
        Console.WriteLine((f) + (f) * (f) / ((f) - (f=2)-1));
        Console.WriteLine(1d + 1d * 1d / (1d - 2d - 1d));

这些console.writeline语句的配对包括代数式(使用数字技巧)和数值表示,显示计算实际执行的内容。这些配对产生相同的结果。

可以看到,参数按顺序进行评估,分配后的任何参数都是2,而分配前的参数都是1。因此,我认为事物的评估顺序是简单的从左到右,但计算顺序是您所期望的。

我认为这几乎可以通过复制和粘贴在JAVA中测试...

如果有人发现这里存在逻辑缺陷,请指出来,我会加以解决。


你的树有误。乘法应该在除法下面的左侧。尝试当 b=20,c=1,d=20 和 e=0 时的情况。 - ILMTitan
不,Chris,它是a+(b*c)/(d-e),而不是a+b*(c/(d-e))。因为*、/和%具有左结合性。 - Edgar Bonet
ILMTitan: 可能是使用整数来处理这些数字,这样c/(d-e)就会被截断为零,显示出差异。我还没有在代码中尝试过,但我知道它是如何工作的。 :) Edgar Bonet: 好的,是的。我明白你的意思了。似乎我没有进行足够的规格检查。很有道理,有一个规则。 :) 我会更新我的华丽ASCII艺术...;-) - Chris

0

我假设你的表达式应该是这样的:

x = a + b * c / (d - e)

等号运算符具有从右到左的评估顺序。因此,在=右侧的表达式将首先进行评估。

如果您参考这个优先级图表:http://www.java-tips.org/java-se-tips/java.lang/what-is-java-operator-precedence.html

1)括号将被计算(d-e),假设(d-e)= f,因此表达式变为x = a + b * c / f。

2)现在*和/具有相同的优先级,但是计算顺序是从左到右,因此*将首先进行计算,假设b * c = g,则表达式变为x = a + g / f

3)现在/具有下一个优先级,因此g / f将被计算为假设h,因此表达式将变为x = a + h,

4)最后计算a + h


“the equality operator”? 你是指“赋值运算符”。 - Alin Purcaru

0
在你的第二个问题中,似乎Java将括号中的部分作为赋值而不是数学表达式进行评估。这意味着它不会按照括号中的操作顺序执行括号赋值。

1
赋值语句是表达式! - Edgar Bonet

-1
计算结果由运算符优先级定义。因此,括号具有最高的优先级,乘法和除法次之,加法和减法最低。具有相同优先级的运算符从左到右进行评估。因此,问题中的表达式等效于:
    a + (b  * c) / ( d - e ))

然而,“首先被评估”通常意味着与获取正确答案的运算符优先级之间存在轻微差异。

在计算“d-e”之前不一定会实际计算“a”。这基本上没有任何区别,除非表达式中的一个“变量”实际上是一个函数。Java标准未指定表达式组件的评估顺序。


2
你的链接文字似乎与其链接到的文字产生了矛盾。它仅包括一份风格指南,指出过度依赖可能会导致代码混淆。 - ILMTitan

-1
a + b * c / ( d - e )
      1         1
          2
  3

运算符优先级的整个目的是将表达式转换为语法树。在这里,*-处于树的同一级别。哪一个首先被评估对结果无关紧要并且不受保证编辑:抱歉,我被我的C背景搞混了。正如其他人指出的那样,Java有一个“左操作数优先计算”的规则。将此规则应用于/可以告诉您首先评估*(您的第一个答案)。

  • 和 / 的优先级不同吗?有没有理由相信它会在除法之前进行乘法,或者只是因为从左到右将 * 放在 / 之前?
- Chris
Java 对于优先级相同的运算符强制执行从左到右的顺序。 - Grodriguez
Java规范中得知:“[*,/和%]具有相同的优先级,并且在语法上是左结合的(它们从左到右分组)。” - Edgar Bonet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接