ทำไมสิ่งนี้ถึงได้วนซ้ำไม่สิ้นสุด?


493

ฉันมีรหัสต่อไปนี้:

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

เรารู้ว่าเขาควรจะเขียนแค่x++หรือx=x+1แต่x = x++ก่อนนั้นมันควรแอตทริบิวต์xกับตัวเองและเพิ่มขึ้นในภายหลัง ทำไมถึงxดำเนินต่อด้วย0คุณค่า

--update

นี่คือ bytecode:

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

}

ฉันจะอ่านเกี่ยวกับคำแนะนำในการพยายามทำความเข้าใจ ...


8
ฉันสงสัยว่าเกิดอะไรขึ้น: 1. โหลด x เข้าสู่การลงทะเบียน (= 0); 2. การเพิ่ม x (x = 1); 3. บันทึกค่าลงทะเบียนเป็น x (x = 0) ใน C / C ++ นี่จะเป็นพฤติกรรมที่ไม่ได้กำหนดเนื่องจากไม่มีจุดลำดับอย่างเป็นทางการในการกำหนดลำดับที่ 2 และ 3 หวังว่าจะมีคนพูดอะไรบางอย่างที่เทียบเท่ากับ Java spec
โฟโต้

19
เราลองทำสิ่งนี้ใน C ++ เพื่อดูว่าจะเกิดอะไรขึ้นและมันพิมพ์ 1,2,3 และออก ฉันไม่ได้คาดหวังว่า ฉันคิดว่ามันขึ้นอยู่กับคอมไพเลอร์เนื่องจากมันเป็นพฤติกรรมที่ไม่ได้กำหนด เราใช้ gnu g ++
เสียใจ

13
@saj x++เป็นการเพิ่มภายหลัง x=คือการกำหนดผล ; ผลของการx++เป็นต้นฉบับx(และมีผลข้างเคียงของการเพิ่มขึ้น แต่ที่ไม่เปลี่ยนผล) ดังนั้นนี้สามารถตีความได้ว่าvar tmp = x; x++; x = tmp;
Marc Gravell

5
ตอนนี้ฉันมีคำถามยอดนิยมที่ฉันเสียใจคำตอบ (ซ้ำ) ไม่หยุดแม้หลังจากเลือกคำตอบที่ถูกต้องแล้ว หน้าจอ "กิจกรรมล่าสุด" ของฉันเต็มไปด้วยคำตอบเดียวกันและมาอีก ...
Tom Brito

3
@ Rob Vermeulen คุณอาจต้องการอ่านคำถามเต็มก่อนที่จะแสดงความคิดเห็น .. ;) นี่คือรหัสที่ทำโดยนักเรียนของฉันและฉันก็อยากรู้ว่าทำไมพฤติกรรมนี้
Tom Brito

คำตอบ:


354

หมายเหตุ : เดิมฉันโพสต์รหัส C # ในคำตอบนี้เพื่อวัตถุประสงค์ในการภาพประกอบเนื่องจาก C # ช่วยให้คุณสามารถส่งintพารามิเตอร์โดยอ้างอิงกับrefคำหลัก ฉันตัดสินใจที่จะอัปเดตด้วยรหัส Java ตามกฎหมายจริงโดยใช้MutableIntชั้นหนึ่งที่ฉันพบใน Google เพื่อจัดเรียงโดยประมาณของสิ่งที่refทำใน C # ฉันไม่สามารถบอกได้จริง ๆ ว่าสิ่งนั้นช่วยหรือทำร้ายคำตอบ ฉันจะบอกว่าฉันเองยังไม่ได้ทำการพัฒนา Java ทั้งหมด ดังนั้นสำหรับทั้งหมดที่ฉันรู้อาจมีวิธีสำนวนมากกว่านี้เพื่อแสดงจุดนี้


บางทีถ้าเราเขียนวิธีการที่จะทำสิ่งที่เท่าเทียมกันx++มันจะทำให้ชัดเจนขึ้น

public MutableInt postIncrement(MutableInt x) {
    int valueBeforeIncrement = x.intValue();
    x.add(1);
    return new MutableInt(valueBeforeIncrement);
}

ขวา? เพิ่มค่าที่ส่งผ่านและส่งคืนค่าดั้งเดิมนั่นคือคำจำกัดความของโอเปอเรเตอร์หลังการทำ

ตอนนี้มาดูกันว่าพฤติกรรมนี้เล่นในโค้ดตัวอย่างของคุณได้อย่างไร:

MutableInt x = new MutableInt();
x = postIncrement(x);

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.

การตรึงของคุณเกี่ยวกับความจริงที่ว่าเมื่อคุณแทนที่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.

ดังนั้นชัดเจน: x = x++ไม่มีผลต่อการเปลี่ยนแปลงค่าของ x มันทำให้ x มีค่า x 0เสมอแล้ว x 0 + 1 และ x 0อีกครั้ง


Update : อนึ่งเพื่อไม่ให้คุณสงสัยว่า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);
        }
    }
}

ด้านล่างเป็นข้อความที่ตัดตอนมาจากผลลัพธ์ของโปรแกรมด้านบน สังเกตเห็นการเกิดขึ้นที่ผิดปกติของทั้ง 1s และ 0s

กำลังเริ่มต้นเธรดพื้นหลัง ...
0
0
1
1
0
0
0
0
0
0
0
0
0
0
1
0
1

1
คุณไม่จำเป็นต้องสร้างคลาสเพื่อส่งต่อโดยอ้างอิงใน java (แม้ว่ามันจะใช้งานได้) คุณสามารถใช้Integerชั้นซึ่งเป็นส่วนหนึ่งของห้องสมุดมาตรฐานและมันยังมีประโยชน์ในการเป็นอัตโนมัติชนิดบรรจุกล่องไปและกลับจากint เกือบโปร่งใส
rmeador

3
@rmeador Integer ไม่เปลี่ยนรูปดังนั้นคุณยังไม่สามารถเปลี่ยนค่าได้ อย่างไรก็ตาม AtomicInteger ไม่แน่นอน
ILMTitan

5
@Dan: โดยวิธีการxในตัวอย่างสุดท้ายของคุณจะต้องมีการประกาศvolatileมิฉะนั้นมันเป็นพฤติกรรมที่ไม่ได้กำหนดและเห็นว่า1มีการใช้งานที่เฉพาะเจาะจง
axtavt

4
@burkestar: ผมไม่คิดว่าลิงค์ที่ค่อนข้างเหมาะสมในกรณีนี้เพราะมันเป็นคำถามที่ชวาและ (ถ้าผมเข้าใจผิด) ลักษณะการทำงานจะไม่ได้กำหนดจริงใน C ++
ด่านเทา

5
@Tom Brito - ใน C ไม่ได้กำหนด ... ++ สามารถทำได้ก่อนหรือหลังการมอบหมาย พูดจริงอาจมีคอมไพเลอร์ที่ทำสิ่งเดียวกับ Java แต่คุณไม่ต้องการเดิมพัน
Detly

170

x = x++ ทำงานในวิธีต่อไปนี้:

  • x++ครั้งแรกจะประเมินการแสดงออก การประเมินผลของการแสดงออกนี้ก่อให้เกิดมูลค่าการแสดงออก (ซึ่งเป็นค่าของการxเพิ่มขึ้นมาก่อน) xและเพิ่มขึ้น
  • หลังจากนั้นจะกำหนดค่านิพจน์ให้กับxแทนที่ค่าที่เพิ่มขึ้น

ดังนั้นลำดับของเหตุการณ์จะเป็นดังนี้ (เป็นรหัสไบต์ที่ถอดรหัสจริงตามที่ผลิตโดยjavap -cพร้อมกับความคิดเห็นของฉัน):

   8: iload_1 // จำค่าปัจจุบันของ x ในสแต็ก
   9: iinc 1, 1 // เพิ่ม x (ไม่เปลี่ยนสแต็ค)
   12: istore_1 // เขียนค่าที่จำได้จากสแต็กถึง x

สำหรับการเปรียบเทียบx = ++x:

   8: iinc 1, 1 // เพิ่ม x
   11: iload_1 // กดค่า x ลงบนสแต็ก
   12: istore_1 // ค่าป๊อปจากสแต็กถึง x

หากคุณทำการทดสอบคุณจะเห็นว่ามันเพิ่มขึ้นครั้งแรกและคุณลักษณะภายหลัง ดังนั้นจึงไม่ควรเป็นศูนย์
Tom Brito

2
@ Tom เป็นประเด็น แต่เนื่องจาก - นี่เป็นลำดับเดียวทั้งหมดมันกำลังทำสิ่งต่าง ๆ ตามลำดับที่ไม่ชัดเจน (และอาจไม่ชัดเจน) โดยการพยายามทดสอบสิ่งนี้คุณกำลังเพิ่มจุดลำดับและรับพฤติกรรมที่แตกต่าง
โฟโต้

เกี่ยวกับเอาต์พุต bytecode ของคุณ: โปรดทราบว่าการiincเพิ่มขึ้นของตัวแปรมันจะไม่เพิ่มค่าสแต็กและจะไม่ทิ้งค่าไว้ในสแต็ก คุณอาจต้องการเพิ่มรหัสที่สร้างโดย++xเพื่อการเปรียบเทียบ
Anon

3
@Rep มันอาจไม่ถูกกำหนดใน C หรือ C ++ แต่ใน Java มันถูกกำหนดไว้อย่างดี
ILMTitan

1
บทความที่น่าสนใจangelikalanger.com/Articles/VSJ/SequencePoints/ …
Jaydee

104

สิ่งนี้เกิดขึ้นเพราะค่าของการxไม่เพิ่มขึ้นเลย

x = x++;

เทียบเท่ากับ

int temp = x;
x++;
x = temp;

คำอธิบาย:

ลองดูรหัสไบต์สำหรับการดำเนินการนี้ พิจารณาคลาสตัวอย่าง:

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

ตอนนี้เราเรียกใช้ disassembler สำหรับคลาสนี้:

$ 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
}

ตอนนี้Java VMเป็นแบบสแต็กซึ่งหมายความว่าสำหรับการดำเนินการแต่ละครั้งข้อมูลจะถูกส่งไปยังสแต็กและจากสแต็คข้อมูลจะปรากฏขึ้นเพื่อดำเนินการ นอกจากนี้ยังมีโครงสร้างข้อมูลอื่นโดยทั่วไปแล้วอาเรย์จะเก็บตัวแปรภายในเครื่อง ตัวแปรท้องถิ่นจะได้รับรหัสซึ่งเป็นเพียงดัชนีไปยังอาร์เรย์

ให้เราดูวิธีช่วยในการจำmain() :

  • iconst_0: ค่าคงที่0 จะถูกส่งไปยังสแต็ก
  • istore_1: องค์ประกอบด้านบนของสแต็คจะโผล่ออกมาและเก็บไว้ในตัวแปรท้องถิ่นที่มีดัชนี ซึ่งเป็น1
    x
  • iload_1: ค่าที่ตำแหน่ง1ซึ่งเป็นค่าx ที่0ถูกผลักเข้าไปในสแต็ก
  • iinc 1, 1: ค่าที่ตั้งของหน่วยความจำที่จะเพิ่มขึ้นโดย1 1ดังนั้นตอนนี้กลายเป็นx 1
  • istore_1: 1ค่าที่ด้านบนของสแต็คที่ถูกเก็บไว้ในสถานที่ตั้งของหน่วยความจำ ที่0ได้รับมอบหมายให้x เขียนทับมูลค่าที่เพิ่มขึ้น

ดังนั้นค่าของxจะไม่เปลี่ยนแปลงส่งผลให้วงวนไม่สิ้นสุด


5
ที่จริงแล้วมันเพิ่มขึ้น (นั่นคือความหมายของ++) แต่ตัวแปรจะถูกเขียนทับในภายหลัง
Progman

10
int temp = x; x = x + 1; x = temp;เป็นการดีกว่าที่จะไม่ใช้คำซ้ำซ้อนในตัวอย่างของคุณ
Scott Chamberlain

52
  1. สัญลักษณ์คำนำหน้าจะเพิ่มตัวแปรก่อนที่จะมีการประเมินการแสดงออก
  2. เครื่องหมาย postfix จะเพิ่มขึ้นหลังจากการประเมินผลนิพจน์

อย่างไรก็ตาม " =" มีโอเปอเรเตอร์ที่ต่ำกว่า " ++"

ดังนั้นx=x++;ควรประเมินดังนี้

  1. x เตรียมพร้อมสำหรับการมอบหมาย (ประเมินแล้ว)
  2. x เพิ่มขึ้น
  3. ค่าก่อนหน้านี้ได้รับมอบหมายให้xx

นี่คือคำตอบที่ดีที่สุด มาร์กอัปบางรายการจะช่วยให้โดดเด่นขึ้นอีกเล็กน้อย
Justin Force

1
นี่เป็นสิ่งที่ผิด มันไม่เกี่ยวกับความสำคัญ ++มีความสำคัญสูงกว่า=ใน C และ C ++ แต่คำสั่งไม่ได้กำหนดในภาษาเหล่านั้น
Matthew Flaschen

2
คำถามเดิมเกี่ยวกับ Java
Jaydee

34

ไม่มีคำตอบที่ค่อนข้างตรงดังนั้นที่นี่ไป:

เมื่อคุณเขียนint x = x++คุณไม่ได้กำหนดxตัวเองให้เป็นค่าใหม่คุณกำลังกำหนดxให้เป็นค่าส่งคืนของx++นิพจน์ ซึ่งเกิดขึ้นเป็นค่าเดิมของxเป็นนัยในคำตอบที่โคลินของ Cochrane

เพื่อความสนุกทดสอบรหัสต่อไปนี้:

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

ผลจะเป็นอย่างไร

0
1

ค่าส่งคืนของนิพจน์เป็นค่าเริ่มต้นxซึ่งเป็นศูนย์ แต่ต่อมาเมื่ออ่านค่าของxเราได้รับการปรับปรุงค่านั่นคือหนึ่ง


ฉันจะพยายามทำความเข้าใจกับบรรทัด bytecode ดูการอัปเดตของฉันดังนั้นมันจะชัดเจน .. :)
Tom Brito

การใช้ println () มีประโยชน์มากสำหรับฉันในการทำความเข้าใจสิ่งนี้
ErikE

29

มันได้รับการอธิบายอย่างดีจากคนอื่น ๆ ฉันเพิ่งรวมลิงก์ไปยังส่วนข้อมูลจำเพาะ Java ที่เกี่ยวข้อง

x = x ++ เป็นนิพจน์ Java จะทำตามคำสั่งการประเมินผล มันจะเป็นครั้งแรกในการประเมินการแสดงออก x ++ ซึ่งจะเพิ่มขึ้น x และความคุ้มค่าผลตั้งค่าก่อนหน้าของ x จากนั้นมันจะกำหนดผลลัพธ์ของนิพจน์ให้กับตัวแปร x ในตอนท้าย, x กลับมาที่ค่าก่อนหน้า


1
+1 นี่คือคำตอบที่ดีที่สุดสำหรับคำถามที่เกิดขึ้นจริง "ทำไม"
Matthew Flaschen

18

คำสั่งนี้:

x = x++;

ประเมินเช่นนี้

  1. กดxลงบนสแต็ก
  2. เพิ่มขึ้นx;
  3. โผล่xออกมาจากกอง

ดังนั้นค่าจึงไม่เปลี่ยนแปลง เปรียบเทียบกับ:

x = ++x;

ซึ่งประเมินว่า:

  1. เพิ่มขึ้นx;
  2. กดxลงบนสแต็ก
  3. โผล่xออกมาจากกอง

สิ่งที่คุณต้องการคือ:

while (x < 3) {
  x++;
  System.out.println(x);
}

13
การใช้งานที่ถูกต้องแน่นอน แต่คำถามคือ 'ทำไม'
p.campbell

1
รหัสต้นฉบับกำลังใช้การเพิ่มขึ้นภายหลังบน x จากนั้นกำหนดให้กับ x x จะถูกผูกไว้กับ x ก่อนที่จะเพิ่มขึ้นดังนั้นมันจะไม่เปลี่ยนค่า
wkl

5
@cletus ฉันไม่ใช่ downvoter แต่คำตอบเริ่มต้นของคุณไม่มีคำอธิบาย มันแค่บอกว่าทำ 'x ++'
Petar Minchev

4
@cletus: ฉันไม่ได้ลงคะแนน แต่เดิมคำตอบของคุณเป็นเพียงx++ข้อมูลโค้ด
p.campbell

10
คำอธิบายไม่ถูกต้องเช่นกัน หากรหัสแรกกำหนดให้ x เป็น x จากนั้นเพิ่มค่า x มันจะทำงานได้ดี เพียงแค่เปลี่ยนx++;วิธีแก้ปัญหาของคุณเป็นx=x; x++;และคุณกำลังทำสิ่งที่คุณอ้างว่ารหัสต้นฉบับกำลังทำอยู่
Wooble

10

คำตอบนั้นค่อนข้างตรงไปตรงมา มันจะทำอย่างไรกับสิ่งที่สั่งซื้อมีการประเมิน x++ผลตอบแทนที่คุ้มค่าแล้วเพิ่มขึ้นxx

ดังนั้นมูลค่าของการแสดงออกเป็นx++ 0ดังนั้นคุณจะกำหนดx=0แต่ละครั้งในวง x++เพิ่มค่านี้แน่นอนแต่จะเกิดขึ้นก่อนการกำหนด


1
ว้าวมีรายละเอียดมากมายในหน้านี้เมื่อคำตอบนั้นสั้นและง่ายมากเช่นนี้
ชาร์ลส์กูดวิน

8

จากhttp://download.oracle.com/javase/tutorial/java/nutsandbolts/op1.html

ตัวดำเนินการที่เพิ่มขึ้น / ลดลงสามารถนำไปใช้ก่อน (คำนำหน้า) หรือหลัง (postfix) ตัวถูกดำเนินการ โค้ดผลลัพธ์ ++; และผลลัพธ์ ++; ทั้งสองจะสิ้นสุดในผลลัพธ์ที่เพิ่มขึ้นโดยหนึ่ง แตกต่างเพียงว่ารุ่นคำนำหน้า (ผล ++) ประเมินมูลค่าที่เพิ่มขึ้น, ในขณะที่รุ่น postfix นั้น (ผล ++) ประเมินค่าเดิม หากคุณเพิ่งเพิ่ม / ลดระดับง่ายๆก็ไม่สำคัญว่าคุณจะเลือกรุ่นใด แต่ถ้าคุณใช้โอเปอเรเตอร์นี้ในส่วนของนิพจน์ที่ใหญ่กว่าอันที่คุณเลือกอาจสร้างความแตกต่างอย่างมีนัยสำคัญ

หากต้องการแสดงตัวอย่างลองทำสิ่งต่อไปนี้:

    int x = 0;
    int y = 0;
    y = x++;
    System.out.println(x);
    System.out.println(y);

ซึ่งจะพิมพ์ 1 และ 0


1
ไม่ใช่ผลการประเมินที่เป็นปัญหา แต่เป็นคำสั่งของร้านค้า
Rup

2
ฉันไม่เห็นด้วย. ถ้า x = 0 ดังนั้น x ++ จะส่งกลับ 0 ดังนั้น x = x ++ จะส่งผลให้ x = 0
Colin Cochrane

Rup ถูกต้องเกี่ยวกับเรื่องนี้ มันเป็นคำสั่งของร้านค้าที่มีปัญหาในกรณีนี้โดยเฉพาะ y = x ++ ไม่เหมือนกับ x = x ++; ในส่วนหลัง x จะถูกกำหนด 2 ค่าในนิพจน์เดียวกัน มือซ้าย x กำลังถูกกำหนดผลลัพธ์ของการประเมินผลของนิพจน์ x ++ ซึ่งคือ 0 ด้านขวา x กำลังเพิ่มขึ้นเป็น 1 ซึ่งการมอบหมาย 2 สิ่งนี้เกิดขึ้นตามลำดับ จากการโพสต์ก่อนหน้านี้เป็นที่ชัดเจนว่าวิธีการทำงานนี้คือ: eval = x ++ => eval == 0: การเพิ่มขึ้นทางขวา x => x == 1: left x = eval => x == 0
Michael Ekoka

7

คุณได้รับพฤติกรรมดังต่อไปนี้อย่างมีประสิทธิภาพ

  1. คว้าค่าของ x (ซึ่งคือ 0) เป็น "ผลลัพธ์" ของด้านขวา
  2. เพิ่มค่าของ x (ดังนั้น x ตอนนี้ 1)
  3. กำหนดผลลัพธ์ของด้านขวา (ซึ่งบันทึกเป็น 0) ให้กับ x (x ตอนนี้เป็น 0)

แนวคิดที่ว่าตัวดำเนินการภายหลังการเพิ่ม (x ++) การเพิ่มขึ้นของตัวแปรในคำถามหลังจากคืนค่าของมันสำหรับใช้ในสมการที่ใช้

แก้ไข: การเพิ่มเล็กน้อยเนื่องจากความคิดเห็น พิจารณามันเหมือนดังต่อไปนี้

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.

ตกลง แต่สิ่งที่ระบุลำดับของขั้นตอนที่ 2 และ 3 คืออะไร
Rup

@Rup - ภาษากำหนดไว้ ด้านขวาของสมการจะถูกประเมินก่อน (ในกรณีนี้ "x ++") และผลลัพธ์จะถูกกำหนดให้กับตัวแปรทางด้านซ้าย นั่นคือวิธีการทำงานของภาษา เท่าที่ "x ++" "กลับมา" x สำหรับสมการนั่นเป็นวิธีที่ผู้ประกอบการเพิ่ม postfix ทำงาน (คืนค่า x แล้วเพิ่มมัน) ถ้ามันเป็น "--x" ก็จะเป็น (เพิ่ม x แล้วส่งกลับค่า) การส่งคืนไม่ใช่คำที่ถูกต้องที่นั่น แต่คุณได้รับแนวคิด
RHSeeger

7

คุณไม่จำเป็นต้องใช้รหัสเครื่องเพื่อทำความเข้าใจว่าเกิดอะไรขึ้น

ตามคำจำกัดความ:

  1. ผู้ประกอบการที่ได้รับมอบหมายประเมินการแสดงออกทางด้านขวามือและเก็บไว้ในตัวแปรชั่วคราว

    1.1 ค่าปัจจุบันของ x ถูกคัดลอกไปยังตัวแปรชั่วคราวนี้

    1.2 x เพิ่มขึ้นในขณะนี้

  2. ตัวแปรชั่วคราวจะถูกคัดลอกไปยังด้านซ้ายของนิพจน์ซึ่งก็คือ x โดยบังเอิญ! นั่นคือสาเหตุที่ค่าเก่าของ x ถูกคัดลอกไปยังตัวมันเองอีกครั้ง

มันค่อนข้างง่าย


5

นี่เป็นเพราะมันไม่เคยเพิ่มขึ้นในกรณีนี้ x++จะใช้ค่าของมันก่อนที่จะเพิ่มขึ้นเช่นในกรณีนี้มันจะเป็นเช่น:

x = 0;

แต่ถ้าคุณทำเช่น++x;นี้จะเพิ่มขึ้น


หากคุณทำการทดสอบคุณจะเห็นว่ามันเพิ่มขึ้นครั้งแรกและคุณลักษณะภายหลัง ดังนั้นจึงไม่ควรเป็นศูนย์
Tom Brito

1
@ ทอม: ดูคำตอบของฉัน - ฉันแสดงในการทดสอบว่า x ++ จริง ๆ แล้วคืนค่าเก่าของ x นั่นคือสิ่งที่มันทำลาย
Robert Munteanu

"ถ้าคุณทำการทดสอบ" - บางคนคิดว่าการทดสอบที่เขียนใน C บอกเราว่า Java จะทำอะไรเมื่อไม่ได้บอกเราว่า C จะทำอะไร
Jim Balter

3

ค่ายังคงอยู่ที่ 0 เพราะค่าx++เป็น 0 ในกรณีนี้มันไม่สำคัญว่ามูลค่าของxจะเพิ่มขึ้นหรือไม่การx=0ดำเนินการมอบหมาย สิ่งนี้จะเขียนทับค่าที่เพิ่มขึ้นชั่วคราวของx(ซึ่งคือ 1 สำหรับ "เวลาที่สั้นมาก")


แต่ x ++ เป็นการดำเนินการโพสต์ ดังนั้น x จะต้องเพิ่มขึ้นหลังจากการมอบหมายเสร็จสมบูรณ์
ซาการ์ V

1
@Sagar V: สำหรับนิพจน์x++เท่านั้นไม่ใช่สำหรับการมอบหมายทั้งหมดx=x++;
Progman

ไม่ฉันคิดว่ามันต้องเพิ่มขึ้นหลังจากอ่านค่าของ x ที่จะใช้ในการมอบหมายเท่านั้น
โฟโต้

1

วิธีนี้ใช้งานได้ตามที่คุณคาดหวัง มันเป็นความแตกต่างระหว่างคำนำหน้าและ postfix

int x = 0; 
while (x < 3)    x = (++x);

1

คิดว่า x ++ เป็นฟังก์ชันที่เรียกว่า "ส่งคืน" สิ่งที่ X คือก่อนการเพิ่ม (นั่นคือสาเหตุที่เรียกว่าการเพิ่มภายหลัง)

ดังนั้นลำดับการดำเนินการคือ:
1: แคชค่าของ x ก่อนการเพิ่ม
2: การเพิ่ม x
3: คืนค่าแคช (x ก่อนที่จะเพิ่มขึ้น)
4: กำหนดค่าส่งคืนให้กับ x


ตกลง แต่สิ่งที่ระบุลำดับของขั้นตอนที่ 3 และ 4 คืออะไร
โฟโต้

"ส่งคืนสิ่งที่ X คือก่อนที่การเพิ่มขึ้น" จะผิดโปรดดูการอัปเดตของฉัน
Tom Brito

ในความเป็นจริงขั้นตอนที่ 3 และ 4 มีการดำเนินงานไม่ได้แยกจากกัน - มันไม่จริงการเรียกฟังก์ชั่นที่ส่งกลับค่ามันก็จะช่วยให้คิดว่ามันเป็นอย่างนั้น เมื่อใดก็ตามที่คุณมีการมอบหมายด้านขวามือคือ "ประเมิน" แล้วผลลัพธ์จะถูกกำหนดไว้ที่ด้านซ้ายมือผลการประเมินอาจคิดว่าเป็นค่าส่งคืนเนื่องจากช่วยให้คุณเข้าใจลำดับของการดำเนินการ แต่ไม่จริง .
jhabbott

โอ๊ะจริง ฉันหมายถึงขั้นตอนที่ 2 และ 4 - เหตุใดค่าที่ส่งคืนจึงเก็บไว้ที่ด้านบนของค่าที่เพิ่มขึ้น
Rup

1
นี่เป็นส่วนหนึ่งของคำนิยามของการดำเนินการที่ได้รับมอบหมายก่อนอื่นทางด้านขวามือจะถูกประเมินอย่างสมบูรณ์จากนั้นจะกำหนดผลลัพธ์ทางด้านซ้ายมือ
jhabbott

1

เมื่อ ++ อยู่บน rhs ผลลัพธ์จะถูกส่งคืนก่อนที่จำนวนจะเพิ่มขึ้น เปลี่ยนเป็น ++ x และมันก็ใช้ได้ดี Java จะมีการเพิ่มประสิทธิภาพนี้เพื่อดำเนินการเดียว (การกำหนดของ x เป็น x) มากกว่าการเพิ่มขึ้น


1

เท่าที่ฉันเห็น, ข้อผิดพลาดเกิดขึ้น, เนื่องจากการกำหนดแทนที่ค่าที่เพิ่มขึ้น, ด้วยค่าก่อนการเพิ่ม, นั่นคือการยกเลิกการเพิ่ม

โดยเฉพาะอย่างยิ่งการแสดงออก "x ++" มีค่าเป็น 'x' ก่อนที่จะเพิ่มขึ้นเมื่อเทียบกับ "++ x" ซึ่งมีค่า 'x' หลังจากการเพิ่ม

หากคุณมีความสนใจในการตรวจสอบ bytecode เราจะดูที่สามบรรทัดในคำถาม:

 7:   iload_1
 8:   iinc    1, 1
11:  istore_1

7: iload_1 # จะใส่ค่าของตัวแปรโลคอลที่ 2 ในสแต็ก
8: iinc 1,1 # จะเพิ่มตัวแปรโลคอลที่ 2 ด้วย 1 โปรดทราบว่ามันจะปล่อยให้สแต็กไม่มีการแตะต้อง!
9: istore_1 # จะปรากฏด้านบนสุดของสแต็กและบันทึกค่าขององค์ประกอบนี้ไปยังตัวแปรโลคอลที่ 2
(คุณสามารถอ่านเอฟเฟกต์ของแต่ละคำสั่ง JVM ได้ที่นี่ )

นี่คือสาเหตุที่โค้ดด้านบนจะวนซ้ำไปเรื่อย ๆ ในขณะที่รุ่นที่มี ++ x จะไม่ทำงาน bytecode สำหรับ ++ x ควรมีลักษณะที่แตกต่างกันมากเท่าที่ฉันจำได้จากคอมไพเลอร์ Java 1.3 ที่ฉันเขียนไปเมื่อหนึ่งปีก่อน bytecode ควรมีลักษณะดังนี้:

iinc 1,1
iload_1
istore_1

ดังนั้นเพียงแค่สลับสองบรรทัดแรกเปลี่ยนความหมายเพื่อให้ค่าที่เหลืออยู่ด้านบนสุดของสแต็คหลังจากการเพิ่มขึ้น (เช่น 'ค่า' ของการแสดงออก) คือค่าหลังจากการเพิ่มขึ้น


1
    x++
=: (x = x + 1) - 1

ดังนั้น:

   x = x++;
=> x = ((x = x + 1) - 1)
=> x = ((x + 1) - 1)
=> x = x; // Doesn't modify x!

แต่ทว่า

   ++x
=: x = x + 1

ดังนั้น:

   x = ++x;
=> x = (x = x + 1)
=> x = x + 1; // Increments x

แน่นอนว่าผลลัพธ์ที่ได้จะเหมือนกับx++;หรือ++x;เป็นแบบเส้นเดียว



0

ฉันสงสัยว่ามีอะไรใน Java spec ที่กำหนดพฤติกรรมของสิ่งนี้ได้อย่างแม่นยำ (ความหมายที่ชัดเจนของข้อความนั้นคือฉันไม่ขี้เกียจเกินกว่าจะตรวจสอบ)

หมายเหตุจากรหัสไบต์ของ Tom เส้นสำคัญคือ 7, 8 และ 11 บรรทัด 7 โหลด x ลงในสแต็กการคำนวณ บรรทัดที่ 8 เพิ่มขึ้น x บรรทัดที่ 11 เก็บค่าจากสแต็กกลับเป็น x ในกรณีปกติที่คุณไม่ได้กำหนดค่าให้กับตัวเองฉันไม่คิดว่าจะมีเหตุผลใด ๆ ที่คุณไม่สามารถโหลดจัดเก็บและเพิ่มขึ้น คุณจะได้รับผลลัพธ์เดียวกัน

กดไลค์สมมติว่าคุณมีกรณีปกติที่คุณเขียนบางอย่างเช่น: z = (x ++) + (y ++);

ไม่ว่าจะเป็นการพูด (pseudocode เพื่อข้าม technicalities)

load x
increment x
add y
increment y
store x+y to z

หรือ

load x
add y
store x+y to z
increment x
increment y

ควรไม่เกี่ยวข้อง ฉันคิดว่าการติดตั้งใช้งานไม่ถูกต้อง

ฉันจะระมัดระวังอย่างมากเกี่ยวกับการเขียนโค้ดที่ขึ้นอยู่กับพฤติกรรมนี้ ดูเหมือนว่าขึ้นอยู่กับการใช้งานมากระหว่างฉันกับรอยแตกในสเป็คกับฉัน ครั้งเดียวที่มันจะสร้างความแตกต่างคือถ้าคุณทำอะไรที่บ้าคลั่งอย่างเช่นที่นี่หรือถ้าคุณมีสองเธรดที่ทำงานอยู่และขึ้นอยู่กับลำดับของการประเมินผลภายในนิพจน์


0

ฉันคิดว่าเพราะใน Java ++ มีลำดับความสำคัญสูงกว่า = (การกำหนด) ... ใช่หรือไม่ ดูhttp://www.cs.uwf.edu/~eelsheik/cop2253/resources/op_precedence.html ...

วิธีเดียวกันถ้าคุณเขียน x = x + 1 ... + มีลำดับความสำคัญสูงกว่า = (การกำหนด)


มันไม่ใช่คำถามที่มาก่อน ++มีความสำคัญสูงกว่า=ใน C และ C ++ เช่นกัน แต่คำสั่งไม่ได้กำหนด
Matthew Flaschen

0

แสดงออกประเมินx++ ส่วนหนึ่งมีผลต่อค่าหลังจากการประเมินผลไม่หลังจากที่คำสั่ง ดังนั้นจึงแปลเป็นอย่างมีประสิทธิภาพx++x = x++

int y = x; // evaluation
x = x + 1; // increment part
x = y; // assignment

0

ก่อนที่จะเพิ่มค่าทีละหนึ่งค่าจะถูกกำหนดให้กับตัวแปร


0

มันเกิดขึ้นเพราะโพสต์เพิ่มขึ้น หมายความว่าตัวแปรนั้นเพิ่มขึ้นหลังจากการประเมินนิพจน์

int x = 9;
int y = x++;

x ตอนนี้คือ 10 แต่ y คือ 9 ค่าของ x ก่อนที่จะเพิ่มขึ้น

ดูข้อมูลเพิ่มเติมในความหมายของการโพสต์เพิ่ม


1
ตัวอย่างx/ ของคุณyแตกต่างจากรหัสจริงและความแตกต่างนั้นมีความเกี่ยวข้อง ลิงค์ของคุณไม่ได้พูดถึง Java สำหรับสองภาษาที่ไม่ได้กล่าวถึงคำสั่งในคำถามจะไม่ได้กำหนด
Matthew Flaschen

0

ตรวจสอบรหัสด้านล่าง

    int x=0;
    int temp=x++;
    System.out.println("temp = "+temp);
    x = temp;
    System.out.println("x = "+x);

ผลลัพธ์จะเป็น

temp = 0
x = 0

post incrementวิธีการเพิ่มมูลค่าและผลตอบแทนคุ้มค่าก่อนที่จะเพิ่มขึ้น นั่นคือเหตุผลที่ค่าคือtemp 0แล้วจะเกิดอะไรขึ้นถ้าtemp = iมันอยู่ในลูป (ยกเว้นบรรทัดแรกของโค้ด) เหมือนในคำถาม !!!!


-1

ตัวดำเนินการที่เพิ่มขึ้นจะถูกนำไปใช้กับตัวแปรเดียวกับที่คุณกำหนด นั่นคือการถามถึงปัญหา ฉันแน่ใจว่าคุณสามารถเห็นค่าของตัวแปร x ของคุณในขณะที่รันโปรแกรมนี้ .... ซึ่งควรทำให้ชัดเจนว่าทำไมลูปไม่สิ้นสุด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.