เหตุใดตัวดำเนินการด้านท้ายที่มีเครื่องหมายจุลภาคจึงประเมินเพียงนิพจน์เดียวในกรณีจริง


119

ฉันกำลังเรียนรู้ C ++ ด้วยหนังสือ C ++ Primer และหนึ่งในแบบฝึกหัดในหนังสือเล่มนี้คือ:

อธิบายว่านิพจน์ต่อไปนี้ใช้ทำอะไร: someValue ? ++x, ++y : --x, --y

เรารู้อะไรบ้าง? เราทราบดีว่าตัวดำเนินการ ternary มีลำดับความสำคัญสูงกว่าตัวดำเนินการลูกน้ำ ด้วยตัวดำเนินการไบนารีสิ่งนี้ค่อนข้างเข้าใจได้ง่าย แต่สำหรับตัวดำเนินการด้านท้ายฉันกำลังดิ้นรนเล็กน้อย ด้วยตัวดำเนินการไบนารี "มีลำดับความสำคัญสูงกว่า" หมายความว่าเราสามารถใช้วงเล็บรอบนิพจน์ที่มีลำดับความสำคัญสูงกว่าและจะไม่เปลี่ยนแปลงการดำเนินการ

สำหรับตัวดำเนินการ ternary ฉันจะทำ:

(someValue ? ++x, ++y : --x, --y)

ทำให้เกิดรหัสเดียวกันอย่างมีประสิทธิภาพซึ่งไม่ได้ช่วยฉันในการทำความเข้าใจว่าคอมไพเลอร์จะจัดกลุ่มโค้ดอย่างไร

อย่างไรก็ตามจากการทดสอบกับคอมไพเลอร์ C ++ ฉันรู้ว่านิพจน์นั้นรวบรวมและฉันไม่รู้ว่าตัว:ดำเนินการใดสามารถยืนได้ด้วยตัวเอง ดังนั้นคอมไพเลอร์ดูเหมือนจะตีความตัวดำเนินการ ternary อย่างถูกต้อง

จากนั้นฉันดำเนินการโปรแกรมด้วยสองวิธี:

#include <iostream>

int main()
{
    bool someValue = true;
    int x = 10, y = 10;

    someValue ? ++x, ++y : --x, --y;

    std::cout << x << " " << y << std::endl;
    return 0;
}

ผลลัพธ์ใน:

11 10

ในทางกลับกันsomeValue = falseมันพิมพ์:

9 9

ทำไมจะ c ++ คอมไพเลอร์สร้างรหัสที่จริงสาขาของผู้ประกอบการ ternary เพียงการเพิ่มขึ้นxในขณะที่สำหรับเท็จสาขาของ ternary มัน decrements ทั้งสองxและy?

ฉันไปไกลถึงการใส่วงเล็บรอบ ๆ สาขาจริงเช่นนี้:

someValue ? (++x, ++y) : --x, --y;

11 10แต่ก็ยังส่งผลในการ


5
"ลำดับความสำคัญ" เป็นเพียงปรากฏการณ์ที่เกิดขึ้นใน C ++ อาจจะง่ายกว่าแค่ดูไวยากรณ์ของภาษาโดยตรงและดูว่านิพจน์ทำงานอย่างไร
Kerrek SB

26
เราไม่ได้ดูแลที่มากเกี่ยวกับหลักการ :-) ความจริงที่คุณต้องถามในที่นี้บ่งชี้ว่ารหัสจะไม่ผ่านการตรวจสอบโค้ดโดยเพื่อนโปรแกรมเมอร์ของคุณ ที่ทำให้ความรู้เกี่ยวกับวิธีการนี้จริงทำงานน้อยกว่าที่มีประโยชน์ ถ้าคุณต้องการที่จะมีส่วนร่วมในC ประกวดที่ปกปิดของหลักสูตร
Bo Persson

5
@BoPersson หากไม่มีตัวอย่างเช่นนี้ให้เรียนรู้ผู้ตรวจสอบในอนาคตจะไม่มีวันเรียนรู้ว่าทำไมพวกเขาจึงควรปฏิเสธสิ่งนี้จากการผลิต
Leushenko

8
@Leushenko - ระฆังเตือนควรจะดังต่อไป การเพิ่มและลดหลายครั้งในคำสั่งเดียวกัน (ding, ding, ding!) ตัวดำเนินการ ternary เมื่อคุณสามารถใช้ if-else (ding, ding, ding!) เดี๋ยวก่อนเครื่องหมายจุลภาคเหล่านั้นเป็นตัวดำเนินการลูกน้ำที่น่ากลัวหรือไม่? (ding, DING, DING!) กับโอเปอเรเตอร์ทั้งหมดมีสิ่งที่มีความสำคัญเหนือกว่าหรือไม่? (ding, ding, ding!) ดังนั้นเราจะไม่สามารถใช้สิ่งนั้นได้ แล้วทำไมต้องเสียเวลาคิดว่ามันทำอะไรถ้ามี?
Bo Persson

4
จู้จี้ไมเนอร์: ชื่อ?เป็นผู้ประกอบการที่มีเงื่อนไข คำว่าตัวดำเนินการ ternaryหมายถึงตัวดำเนินการที่มีตัวถูกดำเนินการสามตัว ตัวดำเนินการตามเงื่อนไขเป็นตัวอย่างหนึ่งของตัวดำเนินการตามเงื่อนไข แต่ภาษาสามารถ (ในทางทฤษฎี) มีตัวดำเนินการตามเงื่อนไขหลายตัว
bta

คำตอบ:


122

ดังที่@Raketeกล่าวในคำตอบที่ยอดเยี่ยมของพวกเขานี่เป็นเรื่องยุ่งยาก ขอเพิ่มเติมนิดนึงว่า

ตัวดำเนินการ ternary ต้องมีแบบฟอร์ม:

? การ กำหนดนิพจน์ตรรกะหรือ: นิพจน์

ดังนั้นเราจึงมีการแมปต่อไปนี้:

  • someValue: ตรรกะหรือนิพจน์
  • ++x, ++y: นิพจน์
  • ??? มอบหมาย เป็นนิพจน์--x, --yหรือเท่านั้น--x?

ในความเป็นจริงเป็นเพียง--xเพราะไม่สามารถแยกวิเคราะห์นิพจน์การมอบหมายเป็นสองนิพจน์ที่คั่นด้วยเครื่องหมายจุลภาค (ตามกฎไวยากรณ์ของ C ++) ดังนั้นจึง--x, --yไม่สามารถถือว่าเป็นนิพจน์การมอบหมายได้

ซึ่งส่งผลให้ส่วนนิพจน์ ternary (ตามเงื่อนไข) มีลักษณะดังนี้:

someValue?++x,++y:--x

อาจช่วยได้เพื่อประโยชน์ในการอ่านเพื่อพิจารณา++x,++yคำนวณเป็นถ้าวงเล็บ(++x,++y); สิ่งที่อยู่ระหว่าง?และ:จะถูกจัดลำดับตามเงื่อนไข (ฉันจะวงเล็บไว้สำหรับส่วนที่เหลือของโพสต์)

และประเมินตามลำดับนี้:

  1. someValue?
  2. (++x,++y) หรือ --x (ขึ้นอยู่กับboolผลลัพธ์ของ 1. )

จากนั้นนิพจน์นี้จะถือว่าเป็นนิพจน์ย่อยด้านซ้ายสำหรับตัวดำเนินการลูกน้ำโดยมีนิพจน์ย่อยด้านขวาอยู่ --yดังนี้:

(someValue?(++x,++y):--x), --y;

ซึ่งหมายความว่าด้านซ้ายคือไฟล์ นิพจน์ทิ้ง - ค่าซึ่งหมายความว่ามีการประเมินอย่างแน่นอน แต่จากนั้นเราประเมินด้านขวาและส่งกลับ

จะเกิดอะไรขึ้นเมื่อ someValueคือtrueอะไร?

  1. (someValue?(++x,++y):--x) ดำเนินการและเพิ่มขึ้น xและyเป็น11และ11
  2. นิพจน์ด้านซ้ายจะถูกยกเลิก (แม้ว่าผลข้างเคียงของการเพิ่มจะยังคงอยู่)
  3. เราประเมินด้านขวามือของตัวดำเนินการลูกน้ำ: --yซึ่งจะลดyกลับไปเป็น10

ในการ "แก้ไข" พฤติกรรมคุณสามารถจัดกลุ่ม--x, --yด้วยวงเล็บเพื่อแปลงเป็นนิพจน์หลักซึ่งเป็นรายการที่ถูกต้องสำหรับนิพจน์การกำหนด *:

someValue?++x,++y:(--x, --y);

* เป็นห่วงโซ่ยาวที่ค่อนข้างตลกที่เชื่อมโยงการแสดงออกถึงการมอบหมายงานกลับไปยังนิพจน์หลัก:

การกำหนดนิพจน์ --- (สามารถประกอบด้วย) -> นิพจน์เงื่อนไข -> ตรรกะหรือนิพจน์ -> ตรรกะและนิพจน์ -> รวมหรือนิพจน์ -> เอกสิทธิ์หรือนิพจน์ - -> และนิพจน์ -> ความเท่าเทียมกัน - นิพจน์ -> นิพจน์เชิงสัมพันธ์ -> กะนิพจน์ -> นิพจน์เสริม -> นิพจน์หลายหลาก -> น. นิพจน์ -> การแสดงออก -> unary-expression -> postfix-expression -> primary-expression


10
ขอขอบคุณที่ช่วยแก้ปัญหาในการแก้กฎไวยากรณ์ การทำเช่นนั้นแสดงให้เห็นว่ามีไวยากรณ์ของ C ++ มากกว่าที่คุณจะพบในหนังสือเรียนส่วนใหญ่
sdenham

4
@sdenham: เมื่อมีคนถามว่าทำไม "ภาษาที่เน้นการแสดงออก" จึงดี (เช่นเมื่อใดที่{ ... }สามารถถือเป็นนิพจน์ได้) ตอนนี้ฉันมีคำตอบ => เพื่อหลีกเลี่ยงการแนะนำตัวดำเนินการลูกน้ำซึ่งทำงานในลักษณะที่ยุ่งยากเช่นนี้
Matthieu M.

คุณช่วยให้ลิงค์เพื่ออ่านเกี่ยวกับassignment-expressionโซ่ได้ไหม
MiP

@MiP: ฉันยกมันขึ้นมาจากมาตรฐานตัวเองคุณสามารถค้นหาได้ภายใต้gram.expr
AndyG

88

ว้าวมันยุ่งยาก

คอมไพเลอร์มองว่านิพจน์ของคุณเป็น:

(someValue ? (++x, ++y) : --x), --y;

ตัวดำเนินการ ternary ต้องการ a :ซึ่งไม่สามารถยืนได้ด้วยตัวเองในบริบทนั้น แต่หลังจากนั้นไม่มีเหตุผลว่าทำไมเครื่องหมายจุลภาคควรเป็นของกรณีเท็จ

ตอนนี้มันอาจสมเหตุสมผลมากขึ้นว่าทำไมคุณถึงได้ผลลัพธ์นั้น ถ้าsomeValueเป็นจริงแล้ว++x, ++yและ--yได้รับการดำเนินการซึ่งไม่ได้อย่างมีประสิทธิภาพมีการเปลี่ยนแปลงแต่เพิ่มหนึ่งไปยังyx

ถ้าsomeValueเป็นเท็จแล้ว--xและ--yถูกดำเนินการโดยลดค่าทั้งสองอย่างทีละรายการ


42

เหตุใดคอมไพเลอร์ C ++ จึงสร้างโค้ดที่สำหรับ true-branch ของตัวดำเนินการ ternary เพิ่มขึ้นเท่านั้น x

คุณตีความผิดว่าเกิดอะไรขึ้น true-branch เพิ่มขึ้นทั้งxและy. อย่างไรก็ตามyจะลดลงทันทีหลังจากนั้นโดยไม่มีเงื่อนไข

สิ่งนี้เกิดขึ้นได้อย่างไร: เนื่องจากตัวดำเนินการตามเงื่อนไขมีลำดับความสำคัญสูงกว่าตัวดำเนินการลูกน้ำใน C ++คอมไพเลอร์จะแยกวิเคราะห์นิพจน์ดังนี้:

   (someValue ? ++x, ++y : --x), (--y);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^  ^^^^^

สังเกต "orphaned" --yหลังเครื่องหมายจุลภาค นี่คือสิ่งที่นำไปสู่การลดลงyที่เพิ่มขึ้นในตอนแรก

ฉันไปไกลถึงการใส่วงเล็บรอบ ๆ สาขาจริงเช่นนี้:

someValue ? (++x, ++y) : --x, --y;

คุณมาถูกทาง แต่คุณวงเล็บสาขาผิด: คุณสามารถแก้ไขได้โดยการวงเล็บสาขาอื่นดังนี้:

someValue ? ++x, ++y : (--x, --y);

Demo (พิมพ์ 11 11)


5

ปัญหาของคุณคือนิพจน์ด้านท้ายไม่ได้มีลำดับความสำคัญสูงกว่าเครื่องหมายจุลภาค ในความเป็นจริง C ++ ไม่สามารถอธิบายได้อย่างถูกต้องเพียงแค่ตามลำดับความสำคัญ - และเป็นปฏิสัมพันธ์ระหว่างตัวดำเนินการที่เกี่ยวข้องกับเครื่องหมายจุลภาคที่ซึ่งแบ่งย่อย

a ? b++, c++ : d++

ถือว่าเป็น:

a ? (b++, c++) : d++

(ลูกน้ำทำงานราวกับว่ามีลำดับความสำคัญสูงกว่า) ในทางกลับกัน,

a ? b++ : c++, d++

ถือว่าเป็น:

(a ? b++ : c++), d++

และตัวดำเนินการตามลำดับมีความสำคัญสูงกว่า


ฉันคิดว่านี่ยังอยู่ในขอบเขตของลำดับความสำคัญเนื่องจากมีการแยกวิเคราะห์ที่ถูกต้องเพียงครั้งเดียวสำหรับทางสายกลางใช่ไหม ยังคงเป็นตัวอย่างที่เป็นประโยชน์
sudo rm -rf slash

2

ประเด็นที่ถูกมองข้ามไปในคำตอบ (แม้ว่าจะสัมผัสกับความคิดเห็น) ก็คือตัวดำเนินการตามเงื่อนไขจะถูกใช้อย่างสม่ำเสมอ (ตั้งใจโดยการออกแบบ?) ในโค้ดจริงเป็นทางลัดในการกำหนดค่าหนึ่งในสองค่าให้กับตัวแปร

ดังนั้นบริบทที่ใหญ่กว่าจะเป็น:

whatIreallyWanted = someValue ? ++x, ++y : --x, --y;

ซึ่งไร้สาระบนใบหน้าของมันดังนั้นอาชญากรรมจึงมีมากมาย:

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

1
หนึ่งในการกำหนดของสองตัวแปรเป็นกรณีปกติของผู้ประกอบการ ternary แต่มีอยู่ครั้งเมื่อมันเป็นประโยชน์ที่จะมีรูปแบบการแสดงออกของif(ตัวอย่างเช่นการแสดงออกเพิ่มขึ้นในการวน) บริบทที่ใหญ่ขึ้นอาจเป็นไปได้for (x = 0, y=0; x+y < 100; someValue?(++x, ++y) :( --x, --y))ด้วยลูปที่สามารถปรับเปลี่ยนxและyแยกกันได้
Martin Bonner สนับสนุน Monica

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