ฉันกำลังอ่านข้อความ Java และได้รับรหัสต่อไปนี้:
int[] a = {4,4};
int b = 1;
a[b] = b = 0;
ในข้อความผู้เขียนไม่ได้ให้คำอธิบายที่ชัดเจนและผลของบรรทัดสุดท้ายคือ: a[1] = 0;
ฉันไม่แน่ใจว่าฉันเข้าใจแล้ว: การประเมินเกิดขึ้นได้อย่างไร?
ฉันกำลังอ่านข้อความ Java และได้รับรหัสต่อไปนี้:
int[] a = {4,4};
int b = 1;
a[b] = b = 0;
ในข้อความผู้เขียนไม่ได้ให้คำอธิบายที่ชัดเจนและผลของบรรทัดสุดท้ายคือ: a[1] = 0;
ฉันไม่แน่ใจว่าฉันเข้าใจแล้ว: การประเมินเกิดขึ้นได้อย่างไร?
คำตอบ:
ให้ฉันพูดอย่างชัดเจนเพราะผู้คนเข้าใจผิดตลอดเวลา:
คำสั่งของการประเมินผลของ subexpressions เป็นอิสระของทั้งสองเชื่อมโยงกันและมีความสำคัญ ความสัมพันธ์และลำดับความสำคัญเป็นตัวกำหนดลำดับที่ดำเนินการตัวดำเนินการ แต่ไม่ได้กำหนดว่าจะประเมินนิพจน์ย่อยในลำดับใด คำถามของคุณเกี่ยวกับลำดับในการประเมินนิพจน์ย่อย
พิจารณา A() + B() + C() * D()
. การคูณมีลำดับความสำคัญสูงกว่าการบวกและการบวกคือการเชื่อมโยงด้านซ้ายดังนั้นสิ่งนี้จึงเทียบเท่ากับ(A() + B()) + (C() * D())
แต่การรู้ว่ามีเพียงการบอกคุณว่าการบวกครั้งแรกจะเกิดขึ้นก่อนการบวกครั้งที่สองและการคูณจะเกิดขึ้นก่อนการบวกครั้งที่สอง มันไม่ได้บอกคุณว่าจะเรียก A (), B (), C () และ D () ในลำดับใด! (นอกจากนี้ยังไม่ได้บอกคุณด้วยว่าการคูณเกิดขึ้นก่อนหรือหลังการเพิ่มครั้งแรก) จะเป็นไปได้อย่างสมบูรณ์แบบที่จะปฏิบัติตามกฎของลำดับความสำคัญและการเชื่อมโยงโดยรวบรวมสิ่งนี้เป็น:
d = D() // these four computations can happen in any order
b = B()
c = C()
a = A()
sum = a + b // these two computations can happen in any order
product = c * d
result = sum + product // this has to happen last
มีการปฏิบัติตามกฎของลำดับความสำคัญและการเชื่อมโยงทั้งหมด - การเพิ่มครั้งแรกเกิดขึ้นก่อนการเพิ่มครั้งที่สองและการคูณจะเกิดขึ้นก่อนการเพิ่มครั้งที่สอง เห็นได้ชัดว่าเราสามารถเรียก A (), B (), C () และ D () ตามลำดับใดก็ได้และยังคงปฏิบัติตามกฎของลำดับความสำคัญและการเชื่อมโยง!
เราต้องการกฎ ไม่เกี่ยวข้องกับกฎของลำดับความสำคัญและการเชื่อมโยงเพื่ออธิบายลำดับที่มีการประเมินนิพจน์ย่อย กฎที่เกี่ยวข้องใน Java (และ C #) คือ "นิพจน์ย่อยถูกประเมินจากซ้ายไปขวา" เนื่องจาก A () ปรากฏทางด้านซ้ายของ C () จึงมีการประเมิน A () ก่อนโดยไม่คำนึงถึงข้อเท็จจริงที่ว่า C () เกี่ยวข้องกับการคูณและ A () เกี่ยวข้องกับการบวกเท่านั้น
ตอนนี้คุณมีข้อมูลเพียงพอที่จะตอบคำถามของคุณ ในa[b] = b = 0
กฎของการเชื่อมโยงบอกว่านี่เป็นa[b] = (b = 0);
แต่ไม่ได้หมายความว่าb=0
วิ่งก่อน! กฎของลำดับความสำคัญกล่าวว่าการจัดทำดัชนีมีความสำคัญสูงกว่าการกำหนด แต่ไม่ได้หมายความว่าผู้จัดทำดัชนีจะทำงานก่อนการกำหนดด้านขวาสุดนั่นไม่ได้หมายความว่าทำดัชนีก่อนจะวิ่งขวาสุดที่ได้รับมอบหมาย
(อัปเดต: เวอร์ชันก่อนหน้าของคำตอบนี้มีการละเว้นเล็กน้อยและไม่สำคัญในทางปฏิบัติในส่วนที่ตามมาซึ่งฉันได้แก้ไขแล้วฉันได้เขียนบทความบล็อกที่อธิบายว่าเหตุใดกฎเหล่านี้จึงเหมาะสมใน Java และ C # ที่นี่: https: // ericlippert.com/2019/01/18/indexer-error-cases/ )
ความสำคัญและการเชื่อมโยงกันเพียงบอกเราว่าได้รับมอบหมายของศูนย์ที่จะb
ต้องเกิดขึ้นก่อนที่จะมอบหมายให้a[b]
เพราะการมอบหมายของศูนย์คำนวณค่าที่ได้รับมอบหมายในการดำเนินงานการจัดทำดัชนีที่ ลำดับความสำคัญและการเชื่อมโยงเพียงอย่างเดียวไม่ได้พูดอะไรเกี่ยวกับการa[b]
ประเมินก่อนหน้านี้หรือไม่หรือหลังจากb=0
อีกครั้งนี่ก็เหมือนกับ: A()[B()] = C()
- ทั้งหมดที่เรารู้ก็คือการจัดทำดัชนีจะต้องเกิดขึ้นก่อนการมอบหมาย เราไม่รู้ว่า A (), B () หรือ C () ทำงานก่อนตามลำดับความสำคัญและการเชื่อมโยงในวันที่มีความสำคัญและเชื่อมโยงกันตามเราต้องการกฎอื่นเพื่อบอกเราว่า
กฎก็คืออีกครั้ง "เมื่อคุณมีทางเลือกว่าจะทำอะไรก่อนอื่นให้ไปซ้ายไปขวาเสมอ" อย่างไรก็ตามมีริ้วรอยที่น่าสนใจในสถานการณ์เฉพาะนี้ ผลข้างเคียงของข้อยกเว้นที่เกิดจากการรวบรวมค่าว่างหรือดัชนีนอกช่วงถือเป็นส่วนหนึ่งของการคำนวณทางด้านซ้ายของงานหรือเป็นส่วนหนึ่งของการคำนวณงานเองหรือไม่ Java เลือกอย่างหลัง (แน่นอนว่านี่เป็นความแตกต่างที่มีความสำคัญเฉพาะในกรณีที่รหัสนั้นผิดอยู่แล้วเนื่องจากรหัสที่ถูกต้องไม่หักค่าโมฆะหรือส่งดัชนีที่ไม่ถูกต้องมาตั้งแต่แรก)
แล้วจะเกิดอะไรขึ้น?
a[b]
อยู่ทางด้านซ้ายของb=0
ดังนั้นการa[b]
วิ่งครั้งแรกa[1]
ที่เกิดใน อย่างไรก็ตามการตรวจสอบความถูกต้องของการดำเนินการจัดทำดัชนีนี้ล่าช้าb=0
เกิดขึ้นa
ถูกต้องและa[1]
อยู่ในระยะจะเกิดขึ้นa[1]
เกิดขึ้นล่าสุดดังนั้นแม้ว่าในเรื่องนี้โดยเฉพาะกรณีที่มีรายละเอียดปลีกย่อยบางอย่างที่จะต้องพิจารณาสำหรับผู้ที่หายากกรณีข้อผิดพลาดที่ไม่ควรเกิดขึ้นในรหัสที่ถูกต้องในครั้งแรกโดยทั่วไปคุณสามารถเหตุผล: สิ่งที่จะเกิดขึ้นทางด้านซ้ายก่อนที่จะสิ่งไปทางขวา นั่นคือกฎที่คุณกำลังมองหา การพูดถึงความสำคัญและการเชื่อมโยงมีทั้งความสับสนและไม่เกี่ยวข้อง
ผู้คนเข้าใจสิ่งนี้ผิดตลอดเวลาแม้แต่คนที่ควรรู้ดีกว่า ฉันได้แก้ไขมากเกินไปหนังสือการเขียนโปรแกรมที่ระบุกฎไม่ถูกต้องดังนั้นจึงไม่แปลกใจเลยที่ผู้คนจำนวนมากมีความเชื่อที่ไม่ถูกต้องอย่างสมบูรณ์เกี่ยวกับความสัมพันธ์ระหว่างลำดับความสำคัญ / ความสัมพันธ์และลำดับการประเมินกล่าวคือในความเป็นจริงแล้วไม่มีความสัมพันธ์เช่นนั้น ; พวกเขาเป็นอิสระ
หากหัวข้อนี้คุณสนใจโปรดดูบทความของฉันในหัวข้อนี้เพื่ออ่านเพิ่มเติม:
http://blogs.msdn.com/b/ericlippert/archive/tags/precedence/
เป็นเรื่องเกี่ยวกับ C # แต่สิ่งนี้ส่วนใหญ่ใช้ได้ดีกับ Java
คำตอบที่เชี่ยวชาญของ Eric Lippert นั้นไม่มีประโยชน์อย่างถูกต้องเพราะมันกำลังพูดถึงภาษาอื่น นี่คือ Java โดยที่ข้อกำหนดภาษา Java เป็นคำอธิบายขั้นสุดท้ายของความหมาย โดยเฉพาะอย่างยิ่ง§15.26.1มีความเกี่ยวข้องเนื่องจากอธิบายถึงลำดับการประเมินสำหรับตัว=
ดำเนินการ (เราทุกคนรู้ว่ามันเชื่อมโยงถูกต้องใช่หรือไม่) ตัดทอนให้เหลือเพียงเล็กน้อยที่เราสนใจในคำถามนี้:
หากนิพจน์ตัวถูกดำเนินการด้านซ้ายเป็นนิพจน์การเข้าถึงอาร์เรย์ ( §15.13 ) จำเป็นต้องมีหลายขั้นตอน:
- อันดับแรกนิพจน์ย่อยอ้างอิงอาร์เรย์ของนิพจน์การเข้าถึงอาร์เรย์ตัวถูกดำเนินการทางซ้ายมือจะได้รับการประเมิน หากการประเมินนี้เสร็จสิ้นลงอย่างกะทันหันนิพจน์การมอบหมายจะเสร็จสมบูรณ์ในทันทีด้วยเหตุผลเดียวกัน นิพจน์ย่อยของดัชนี (ของนิพจน์การเข้าถึงอาร์เรย์ตัวถูกดำเนินการทางซ้าย) และตัวถูกดำเนินการทางขวาจะไม่ได้รับการประเมินและไม่มีการกำหนดใด ๆ เกิดขึ้น
- มิฉะนั้นนิพจน์ย่อยดัชนีของนิพจน์การเข้าถึงอาร์เรย์ตัวถูกดำเนินการทางซ้ายจะถูกประเมิน หากการประเมินนี้เสร็จสิ้นลงอย่างกะทันหันนิพจน์การมอบหมายจะเสร็จสมบูรณ์ทันทีด้วยเหตุผลเดียวกันและตัวถูกดำเนินการทางขวามือจะไม่ได้รับการประเมินและไม่มีการมอบหมายใด ๆ เกิดขึ้น
- มิฉะนั้นระบบจะประเมินตัวถูกดำเนินการทางขวามือ หากการประเมินนี้เสร็จสิ้นลงอย่างกะทันหันนิพจน์การมอบหมายจะเสร็จสิ้นทันทีด้วยเหตุผลเดียวกันและไม่มีการมอบหมายใด ๆ เกิดขึ้น
[…จากนั้นจะอธิบายความหมายที่แท้จริงของงานนั้นเองซึ่งเราสามารถเพิกเฉยต่อความสั้นได้…]
ในระยะสั้น Java มีลำดับการประเมินที่กำหนดไว้อย่างใกล้ชิดซึ่งค่อนข้างตรงจากซ้ายไปขวาภายในอาร์กิวเมนต์ของตัวดำเนินการหรือการเรียกเมธอดใด ๆ การกำหนดอาร์เรย์เป็นหนึ่งในกรณีที่ซับซ้อนมากขึ้น แต่ก็ยังมี L2R อยู่ (JLS ไม่แนะนำว่าคุณอย่าเขียนโค้ดที่ต้องการข้อ จำกัด ทางความหมายที่ซับซ้อนประเภทนี้และฉันก็เช่นกัน: คุณสามารถประสบปัญหามากเกินพอด้วยการมอบหมายเพียงครั้งเดียวต่อคำสั่ง!)
C และ C ++ แตกต่างจาก Java ในด้านนี้อย่างแน่นอน: คำจำกัดความภาษาของพวกเขาออกจากลำดับการประเมินโดยไม่ได้กำหนดโดยเจตนาเพื่อให้สามารถปรับแต่งได้มากขึ้น เห็นได้ชัดว่า C # เหมือน Java แต่ฉันไม่รู้จักวรรณกรรมของมันดีพอที่จะชี้ไปที่คำจำกัดความที่เป็นทางการได้ (สิ่งนี้แตกต่างกันไปตามภาษาจริงๆ Ruby เป็น L2R อย่างเคร่งครัดเช่นเดียวกับ Tcl - แม้ว่าจะไม่มีตัวดำเนินการมอบหมายต่อ seด้วยเหตุผลที่ไม่เกี่ยวข้องที่นี่ - และ Python คือL2R แต่ R2L ในแง่ของการมอบหมายซึ่งฉันคิดว่าแปลก แต่เอาล่ะไป .)
a[-1]=c
, c
การประเมินก่อนที่จะ-1
ได้รับการยอมรับในฐานะที่ไม่ถูกต้อง
a[b] = b = 0;
1) ตัวดำเนินการจัดทำดัชนีอาร์เรย์มีลำดับความสำคัญสูงกว่าตัวดำเนินการกำหนด (ดูคำตอบนี้ ):
(a[b]) = b = 0;
2) ตาม 15.26. ผู้ดำเนินการมอบหมายของJLS
มีผู้ดำเนินการกำหนด 12 คน; ทั้งหมดมีการเชื่อมโยงจากขวาไปทางไวยากรณ์ (จัดกลุ่มจากขวาไปซ้าย) ดังนั้น a = b = c หมายถึง a = (b = c) ซึ่งกำหนดค่าของ c ถึง b จากนั้นกำหนดค่าของ b ให้กับ a
(a[b]) = (b=0);
3) ตาม 15.7. ลำดับการประเมินของJLS
ภาษาการเขียนโปรแกรม Java รับประกันว่าตัวถูกดำเนินการของตัวดำเนินการจะได้รับการประเมินตามลำดับการประเมินเฉพาะกล่าวคือจากซ้ายไปขวา
และ
ตัวถูกดำเนินการทางซ้ายมือของตัวดำเนินการไบนารีดูเหมือนจะได้รับการประเมินอย่างสมบูรณ์ก่อนที่จะประเมินส่วนใด ๆ ของตัวถูกดำเนินการทางขวามือ
ดังนั้น:
ก) (a[b])
ประเมินก่อนถึงa[1]
b) แล้ว(b=0)
ประเมินเป็น0
c) (a[1] = 0)
ประเมินล่าสุด
รหัสของคุณเทียบเท่ากับ:
int[] a = {4,4};
int b = 1;
c = b;
b = 0;
a[c] = b;
ซึ่งจะอธิบายผล
ลองพิจารณาตัวอย่างเชิงลึกอื่น ๆ ด้านล่าง
ที่ดีที่สุดคือมีตารางลำดับความสำคัญของกฎและความสัมพันธ์ที่มีให้อ่านเมื่อแก้คำถามเหล่านี้เช่นhttp://introcs.cs.princeton.edu/java/11precedence/
นี่คือตัวอย่างที่ดี:
System.out.println(3+100/10*2-13);
คำถาม: อะไรคือผลลัพธ์ของบรรทัดด้านบน?
คำตอบ: ใช้กฎแห่งความสำคัญและความสัมพันธ์
ขั้นตอนที่ 1: ตามกฎของลำดับความสำคัญ: / และ * ตัวดำเนินการจะมีลำดับความสำคัญมากกว่าตัวดำเนินการ + - ดังนั้นจุดเริ่มต้นในการดำเนินการสมการนี้จะแคบลงเป็น:
100/10*2
ขั้นตอนที่ 2: ตามกฎและลำดับความสำคัญ: / และ * มีความสำคัญเท่ากัน
ตัวดำเนินการในฐานะ / และ * มีความสำคัญเท่ากันเราจำเป็นต้องดูความสัมพันธ์ระหว่างตัวดำเนินการเหล่านั้น
ตามกฎความสัมพันธ์ของตัวดำเนินการสองตัวนี้เราเริ่มดำเนินการสมการจากซ้ายไปขวาคือ 100/10 ได้รับการดำเนินการก่อน:
100/10*2
=100/10
=10*2
=20
ขั้นตอนที่ 3: ขณะนี้สมการอยู่ในสถานะการดำเนินการดังต่อไปนี้:
=3+20-13
ตามกฎและลำดับความสำคัญ: + และ - มีความสำคัญเท่ากัน
ตอนนี้เราต้องดูความสัมพันธ์ระหว่างตัวดำเนินการ + และ - ตัวดำเนินการ ตามความเชื่อมโยงของตัวดำเนินการสองตัวนี้เราเริ่มดำเนินการสมการจากซ้ายไปขวาเช่น 3 + 20 ได้รับการดำเนินการก่อน:
=3+20
=23
=23-13
=10
10 คือผลลัพธ์ที่ถูกต้องเมื่อคอมไพล์
อีกครั้งเป็นสิ่งสำคัญที่จะต้องมีตารางลำดับความสำคัญของกฎและการเชื่อมโยงกับคุณเมื่อแก้ปัญหาเหล่านี้เช่นhttp://introcs.cs.princeton.edu/java/11precedence/
10 - 4 - 3
พยายามที่จะใช้ตรรกะที่และประเมินผลการศึกษา
+
เป็นตัวดำเนินการแบบ unary (ซึ่งมีการเชื่อมโยงจากขวาไปซ้าย) แต่การเติมแต่ง + และ - เหมือนกับการคูณ * /% ทางซ้าย เพื่อการเชื่อมโยงที่เหมาะสม