为什么会进入无限循环?

Tom*_*ito 490 java loops increment operators variable-assignment

我有以下代码:

public class Tests {
    public static void main(String[] args) throws Exception {
        int x = 0;
        while(x<3) {
            x = x++;
            System.out.println(x);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我们知道他应该只是写x++或者x=x+1,但是x = x++它首先要归于x自身,然后再增加它.为什么x继续0作为价值?

--update

这是字节码:

public class Tests extends java.lang.Object{
public Tests();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[])   throws java.lang.Exception;
  Code:
   0:   iconst_0
   1:   istore_1
   2:   iload_1
   3:   iconst_3
   4:   if_icmpge   22
   7:   iload_1
   8:   iinc    1, 1
   11:  istore_1
   12:  getstatic   #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   15:  iload_1
   16:  invokevirtual   #3; //Method java/io/PrintStream.println:(I)V
   19:  goto    2
   22:  return

}
Run Code Online (Sandbox Code Playgroud)

我会读到有关试图理解的说明 ......

Dan*_*Tao 353

注意:最初我在本答案中发布了C#代码用于说明,因为C#允许您int通过引用传递参数和ref关键字.我决定使用MutableInt我在Google上找到的第一个类来实际使用合法的Java代码来更新它,以便对refC#中的内容进行近似处理.我无法确定这是否有助于或伤害答案.我会说我个人没有做过那么多Java开发; 所以我知道可能有更多的惯用方法来说明这一点.


也许如果我们写出一个方法来做相同的事情x++,它会使这更清楚.

public MutableInt postIncrement(MutableInt x) {
    int valueBeforeIncrement = x.intValue();
    x.add(1);
    return new MutableInt(valueBeforeIncrement);
}
Run Code Online (Sandbox Code Playgroud)

对?增加传递的值并返回原始值:这是postincrement运算符的定义.

现在,让我们看看您的示例代码中这种行为是如何发挥作用的:

MutableInt x = new MutableInt();
x = postIncrement(x);
Run Code Online (Sandbox Code Playgroud)

postIncrement(x)做什么?增量x,是的.然后返回什么x 增量之前.然后将此返回值分配给x.

因此,赋值的值的顺序x为0,然后是1,然后是0.

如果我们重写上述内容,这可能会更清楚:

MutableInt x = new MutableInt();    // x is 0.
MutableInt temp = postIncrement(x); // Now x is 1, and temp is 0.
x = temp;                           // Now x is 0 again.
Run Code Online (Sandbox Code Playgroud)

当你x在上面的作业的左侧替换为y"你可以看到它首先递增x,然后将它归因于y" 这一事实时,你固定的事实让我感到困惑.它不是x被分配给y; 它是以前赋予的价值x.实际上,注入y使事情与上面的场景没有什么不同; 我们只是得到:

MutableInt x = new MutableInt();    // x is 0.
MutableInt y = new MutableInt();    // y is 0.
MutableInt temp = postIncrement(x); // Now x is 1, and temp is 0.
y = temp;                           // y is still 0.
Run Code Online (Sandbox Code Playgroud)

所以很清楚:x = x++有效地不会改变x的值.它总是使x的值为x 0,然后是x 0 + 1,然后再次为x 0.


更新:顺便说一句,为了避免你怀疑x在上面的例子中增加操作和赋值之间的分配是1"之间",我已经把一个快速演示组合在一起来说明这个中间值确实"存在",尽管它会永远不会在执行线程上"看到".

演示调用x = x++;循环,而单独的线程连续打印x控制台的值.

public class Main {
    public static volatile int x = 0;

    public static void main(String[] args) {
        LoopingThread t = new LoopingThread();
        System.out.println("Starting background thread...");
        t.start();

        while (true) {
            x = x++;
        }
    }
}

class LoopingThread extends Thread {
    public @Override void run() {
        while (true) {
            System.out.println(Main.x);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

以下是上述程序输出的摘录.注意1和0的不规则出现.

Starting background thread...
0
0
1
1
0
0
0
0
0
0
0
0
0
0
1
0
1

  • @Dan:顺便说一句,你最后一个例子中的`x`必须声明为`volatile`,否则它是一个未定义的行为,并且看到`1`s是特定于实现的. (5认同)
  • @Tom Brito - 在C中它没有定义......`++`*可以在赋值之前或之后完成.实际上,可能有一个编译器与Java做同样的事情,但你不想赌它. (5认同)
  • @burkestar:在这种情况下,我认为链接不是很合适,因为它是一个Java问题而且(除非我弄错了)这个行为在C++中实际上是未定义的. (4认同)
  • @rmeador Integer是不可变的,所以你仍然无法改变它的值.然而,AtomicInteger是可变的. (3认同)

axt*_*avt 169

x = x++ 以下列方式工作:

  • 首先,它评估表达式x++.对该表达式的求值产生表达式值(它是x增量前的值)和增量x.
  • 稍后它将表达式值赋值为x,覆盖递增的值.

因此,事件序列如下所示(它是一个实际的反编译字节码,由javap -c我的评论产生):

   8:   iload_1         // Remember current value of x in the stack
   9:   iinc    1, 1    // Increment x (doesn't change the stack)
   12:  istore_1        // Write remebered value from the stack to x

为了比较,x = ++x:

   8:   iinc    1, 1    // Increment x
   11:  iload_1         // Push value of x onto stack
   12:  istore_1        // Pop value from the stack to x

  • @Rep它可能没有在C或C++中定义,但在Java中,它定义得很好. (3认同)
  • @Tom这就是重点 - 因为这是一个单一的序列,它以非显而易见的(可能是未定义的)顺序进行.通过尝试对此进行测试,您将添加序列点并获得不同的行为. (2认同)

cod*_*ict 104

发生这种情况是因为它的值x根本没有增加.

x = x++;
Run Code Online (Sandbox Code Playgroud)

相当于

int temp = x;
x++;
x = temp;
Run Code Online (Sandbox Code Playgroud)

说明:

我们来看看这个操作的字节代码.考虑一个示例类:

class test {
    public static void main(String[] args) {
        int i=0;
        i=i++;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在运行类反汇编程序,我们得到:

$ javap -c test
Compiled from "test.java"
class test extends java.lang.Object{
test();
  Code:
   0:    aload_0
   1:    invokespecial    #1; //Method java/lang/Object."<init>":()V
   4:    return

public static void main(java.lang.String[]);
  Code:
   0:    iconst_0
   1:    istore_1
   2:    iload_1
   3:    iinc    1, 1
   6:    istore_1
   7:    return
}
Run Code Online (Sandbox Code Playgroud)

现在Java VM是基于堆栈的,这意味着对于每个操作,数据将被推送到堆栈和堆栈,数据将弹出以执行操作.还有另一种数据结构,通常是用于存储局部变量的数组.局部变量给出id,它们只是数组的索引.

让我们来看看助记符main()方法:

  • iconst_0:常量值0 被推送到堆栈.
  • istore_1:弹出堆栈的顶部元素并将其存储在索引1
    为的局部变量 中x.
  • iload_1:在该位置处的值1是值的x0,被压入堆栈.
  • iinc 1, 1:内存位置的1值递增1.所以x现在变成了 1.
  • istore_1:堆栈顶部的值存储在内存位置1.这被0指定为x 覆盖其递增的值.

因此,值x不会改变,从而导致无限循环.

  • `int temp = x; x = x + 1; x = temp;`最好不要在你的例子中使用重言式. (10认同)
  • 实际上它会增加(这就是`++`的含义),但变量会在以后被覆盖. (5认同)

Jay*_*dee 52

  1. 前缀表示法将在计算表达式之前增加变量BEF.
  2. Postfix表示法将在表达式评估后递增.

但是" ="的运算符优先级低于" ++".

所以x=x++;应评估如下

  1. x 准备分配(评估)
  2. x 增加
  3. 以前x分配给的值x.

  • +1为第一个实际解释为什么++ then = (15认同)
  • 最初的问题是关于Java (2认同)

Rob*_*anu 34

没有一个答案在哪里,所以这里是:

当你在写作时int x = x++,你并没有指定x自己处于新值,而是指定xx++表达式的返回值.x正如Colin Cochrane的回答所示,这恰好是原始价值.

为了好玩,请测试以下代码:

public class Autoincrement {
        public static void main(String[] args) {
                int x = 0;
                System.out.println(x++);
                System.out.println(x);
        }
}
Run Code Online (Sandbox Code Playgroud)

结果将是

0
1
Run Code Online (Sandbox Code Playgroud)

表达式的返回值是初始值x,为零.但是稍后,当读取值时x,我们会收到更新的值,即一个.


小智 29

它已经被其他人很好地解释了.我只是包含相关Java规范部分的链接.

x = x ++是一个表达式.Java将遵循评估顺序.它将首先计算表达式x ++,它将增加x并将结果值设置为x的前一个值.然后它将表达式结果赋给变量x.最后,x返回其先前的值.

  • +1。到目前为止,这是对“为什么?”这个实际问题的最佳答案。 (2认同)

cle*_*tus 18

这个说法:

x = x++;
Run Code Online (Sandbox Code Playgroud)

评估如下:

  1. x到堆栈上;
  2. 增量x;
  3. x从堆栈弹出.

所以价值没有变化.比较一下:

x = ++x;
Run Code Online (Sandbox Code Playgroud)

评估为:

  1. 增量x;
  2. x到堆栈上;
  3. x从堆栈弹出.

你想要的是:

while (x < 3) {
  x++;
  System.out.println(x);
}
Run Code Online (Sandbox Code Playgroud)

  • 绝对是正确的实现,但问题是'为什么?'. (13认同)
  • 解释也是不正确的.如果代码首先将x分配给x然后递增x,那么它将正常工作.只需将解决方案中的`x ++;`更改为`x = x; x ++;`并且你正在做你声称原始代码正在做的事情. (10认同)
  • @cletus我不是downvoter,但你的初步答案没有包含解释.它只是说'x ++'. (5认同)
  • @cletus:我没有downvote,但你的回答最初只是`x ++'代码片段. (4认同)
  • 原始代码在 x 上使用后增量,然后将其分配给 x。x 将在递增之前绑定到 x,因此它永远不会改变值。 (2认同)

Mik*_*nes 10

答案很简单.它与评估事物的顺序有关.x++返回值x然后递增x.

因此,表达式的值x++0.所以你x=0在循环中每次分配.当然会x++增加此值,但这在分配之前发生.

  • 哇,当答案简短而简单时,这个页面上有很多细节,即这个。 (2认同)

Col*_*ane 8

来自http://download.oracle.com/javase/tutorial/java/nutsandbolts/op1.html

可以在操作数之前(前缀)或之后(后缀)应用递增/递减运算符.代码结果++; 和++结果; 两个结果都会以一个增加结果.唯一的区别是前缀版本(++结果)评估为增量值,而后缀版本(结果++)评估为原始值.如果您只是执行简单的增量/减量,那么选择哪个版本并不重要.但是,如果您在较大的表达式中使用此运算符,则您选择的运算符可能会产生显着差异.

为了说明,请尝试以下方法:

    int x = 0;
    int y = 0;
    y = x++;
    System.out.println(x);
    System.out.println(y);
Run Code Online (Sandbox Code Playgroud)

其中将打印1和0.

  • 不过,评估结果不是问题,而是商店的顺序。 (2认同)
  • 我不同意.如果x = 0,那么x ++将返回0.因此x = x ++将导致x = 0. (2认同)

RHS*_*ger 7

您有效地获得了以下行为.

  1. 抓取x的值(即0)作为右侧的"结果"
  2. 增加x的值(所以x现在为1)
  3. 将右侧的结果(保存为0)分配给x(x现在为0)

这个想法是后增量运算符(x ++)增加有问题的变量AFTER返回它的值以便在它使用的等式中使用.

编辑:由于评论而添加一点点.考虑如下.

x = 1;        // x == 1
x = x++ * 5;
              // First, the right hand side of the equation is evaluated.
  ==>  x = 1 * 5;    
              // x == 2 at this point, as it "gave" the equation its value of 1
              // and then gets incremented by 1 to 2.
  ==>  x = 5;
              // And then that RightHandSide value is assigned to 
              // the LeftHandSide variable, leaving x with the value of 5.
Run Code Online (Sandbox Code Playgroud)


小智 7

您并不真正需要机器代码来了解正在发生的事情.

根据定义:

  1. 赋值运算符计算右侧表达式,并将其存储在临时变量中.

    1.1.x的当前值被复制到此临时变量中

    1.2.x现在递增.

  2. 然后将临时变量复制到表达式的左侧,这是偶然的x!这就是x的旧值再次复制到自身的原因.

这很简单.


mez*_*zie 5

这是因为在这种情况下它永远不会增加.x++在增加之前将首先使用它的值,就像在这种情况下它会像:

x = 0;
Run Code Online (Sandbox Code Playgroud)

但如果你这样做++x;会增加.

  • @Tom:请看我的回答 - 我在测试中表明 x++ 实际上返回了 x 的旧值。这就是它破裂的地方。 (2认同)