Comma Operator ทำงานอย่างไร


175

เครื่องหมายจุลภาคทำงานอย่างไรใน C ++

ตัวอย่างเช่นถ้าฉัน:

a = b, c;  

ท้ายที่สุดเท่ากับ b หรือ c หรือไม่?

(ใช่ฉันรู้ว่านี่เป็นเรื่องง่ายที่จะทดสอบ - เพียงแค่บันทึกที่นี่เพื่อให้คนอื่นหาคำตอบได้อย่างรวดเร็ว)

อัปเดต: คำถามนี้มีความแตกต่างเล็กน้อยเมื่อใช้โอเปอเรเตอร์คอมม่า เพียงบันทึกเอกสารนี้:

a = b, c;    // a is set to the value of b!

a = (b, c);  // a is set to the value of c!

คำถามนี้ได้รับแรงบันดาลใจจากการพิมพ์ผิดในรหัส สิ่งที่ตั้งใจจะเป็น

a = b;
c = d;

กลายเป็น

a = b,    //  <-  Note comma typo!
c = d;

อ่านเพิ่มเติมได้ที่นี่ stackoverflow.com/questions/12824378/…
เข้ารหัส Mash

1
มีความเป็นไปได้ที่ซ้ำกันของเครื่องหมายโอเปอเรเตอร์ `,` ทำใน C อะไร? . มันชนะคุณวันหนึ่ง และคำตอบ lillq a = (b, c);ให้คำตอบสำหรับคำถามเกี่ยวกับการที่
jww

5
แต่ในกรณีนี้a = b, c = d;จริง ๆ แล้วทำเช่นเดียวกับที่ตั้งใจไว้a = b; c = d;?
Bondolin

@NargothBond ไม่จำเป็น ถ้าbและdมีการประเมินผลการทำงานที่ใช้ (และปรับเปลี่ยน) C++17รัฐร่วมกันเพื่อดำเนินการที่ไม่ได้กำหนดไว้จนกว่า
nyronium

คำตอบ:



129

ใช้ความระมัดระวังเพื่อสังเกตว่าตัวดำเนินการเครื่องหมายจุลภาคอาจโอเวอร์โหลดใน C ++ พฤติกรรมที่แท้จริงอาจแตกต่างจากที่คาดไว้มาก

ตัวอย่างเช่นBoost.Spiritใช้ตัวดำเนินการคอมม่าค่อนข้างฉลาดเพื่อใช้รายการเริ่มต้นสำหรับตารางสัญลักษณ์ ดังนั้นจึงทำให้ไวยากรณ์ต่อไปนี้เป็นไปได้และมีความหมาย:

keywords = "and", "or", "not", "xor";

ขอให้สังเกตว่าเนื่องจากความสำคัญของโอเปอเรเตอร์รหัสจึงเหมือนกัน (โดยเจตนา!)

(((keywords = "and"), "or"), "not"), "xor";

นั่นคือตัวดำเนินการแรกที่เรียกว่าkeywords.operator =("and")ซึ่งจะส่งคืนวัตถุพร็อกซี่ที่operator,มีการเรียก s ที่เหลือ:

keywords.operator =("and").operator ,("or").operator ,("not").operator ,("xor");

อืมคุณไม่สามารถเปลี่ยนลำดับความสำคัญได้ซึ่งหมายความว่าคุณควรใส่วงเล็บในรายการของคุณ
Jeff Burdges

18
@Jeff ตรงกันข้าม ด้วยวงเล็บรอบรายการสิ่งนี้จะไม่ทำงานตั้งแต่นั้นคอมไพเลอร์เพิ่งเห็นตัวดำเนินการเครื่องหมายจุลภาคระหว่างสองchar[]ซึ่งไม่สามารถโอเวอร์โหลด รหัสโดยเจตนาแรกเรียกoperator=แล้วในภายหลังoperator,สำหรับแต่ละองค์ประกอบที่เหลือ
Konrad Rudolph

125

ตัวดำเนินการจุลภาคมีลำดับความสำคัญต่ำสุดของตัวดำเนินการ C / C ++ ทั้งหมด ดังนั้นจึงเป็นรายการสุดท้ายที่ผูกกับนิพจน์ซึ่งหมายความว่า:

a = b, c;

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

(a = b), c;

อีกหนึ่งความจริงที่น่าสนใจคือการที่ผู้ประกอบการจุลภาคแนะนำจุดลำดับ ซึ่งหมายความว่าการแสดงออก:

a+b, c(), d

มีการรับประกันว่าจะมีสามนิพจน์ย่อย ( a + b , c ()และd ) ประเมินตามลำดับ นี่เป็นสิ่งสำคัญหากพวกเขามีผลข้างเคียง โดยปกติคอมไพเลอร์จะได้รับอนุญาตให้ประเมินนิพจน์ย่อยในลำดับใดก็ตามที่พวกเขาพบว่าเหมาะสม ตัวอย่างเช่นในการเรียกใช้ฟังก์ชัน:

someFunc(arg1, arg2, arg3)

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


15
คุ้มค่าที่ชี้ให้เห็นว่า,มีความสำคัญต่ำเช่นนั้นแม้ล่าช้าหลังตัวเอง ;) ... นั่นคือจุลภาคย่คดผู้ประกอบการมีความสำคัญต่ำกว่าจุลภาคย่คดคั่น ดังนั้นถ้าคุณต้องการที่จะใช้จุลภาคย่คดผู้ประกอบการภายในอาร์กิวเมนต์ฟังก์ชันเดียว, การกำหนดตัวแปรหรือจุลภาคอื่น ๆแยกรายการ - แล้วคุณจะต้องใช้วงเล็บเช่น:int a = 1, b = 2, weirdVariable = (++a, b), d = 4;
underscore_d

68

ผู้ประกอบการจุลภาค:

  • มีลำดับความสำคัญต่ำสุด
  • สัมพันธ์กันทางซ้าย

รุ่นเริ่มต้นของผู้ประกอบการเครื่องหมายจุลภาคถูกกำหนดไว้สำหรับทุกประเภท (ในตัวและที่กำหนดเอง) และมันทำงานดังต่อไปนี้ - รับexprA , exprB:

  • exprA ได้รับการประเมิน
  • ผลลัพธ์ของexprAถูกละเว้น
  • exprB ได้รับการประเมิน
  • ผลลัพธ์ของการexprBถูกส่งกลับเป็นผลมาจากการแสดงออกทั้งหมด

สำหรับตัวดำเนินการส่วนใหญ่คอมไพเลอร์จะได้รับอนุญาตให้เลือกลำดับของการดำเนินการและจำเป็นต้องข้ามการดำเนินการใด ๆ หากไม่ส่งผลต่อผลลัพธ์สุดท้าย (เช่นfalse && foo()จะข้ามการเรียกไปfoo) แต่นี้เป็นกรณีที่ไม่ได้สำหรับผู้ประกอบจุลภาคและขั้นตอนข้างต้นมักจะเกิดขึ้น*

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

  • ไวยากรณ์ C ต้องการนิพจน์เดียวไม่ใช่คำสั่ง เช่นในif( HERE )
  • ไวยากรณ์ C ต้องการคำสั่งเดียวไม่มากเช่นในการเริ่มต้นของforลูปfor ( HERE ; ; )
  • เมื่อคุณต้องการข้ามวงเล็บปีกกาและเก็บคำสั่งเดียว: if (foo) HERE ;(โปรดอย่าทำอย่างนั้นมันน่าเกลียดจริงๆ!)

เมื่อคำสั่งไม่ใช่นิพจน์เซมิโคลอนไม่สามารถแทนที่ด้วยเครื่องหมายจุลภาค ตัวอย่างเช่นสิ่งเหล่านี้ไม่ได้รับอนุญาต:

  • (foo, if (foo) bar) (ifไม่ใช่นิพจน์)
  • int x, int y (การประกาศตัวแปรไม่ใช่นิพจน์)

ในกรณีของคุณเรามี:

  • a=b, c;เทียบเท่ากับa=b; c;สมมติว่าaเป็นประเภทที่ไม่เกินตัวดำเนินการเครื่องหมายจุลภาค
  • a = b, c = d;เทียบเท่ากับa=b; c=d;สมมติว่าaเป็นประเภทที่ไม่เกินตัวดำเนินการเครื่องหมายจุลภาค

โปรดทราบว่าไม่ใช่เครื่องหมายจุลภาคทุกอันที่จริงแล้วเป็นเครื่องหมายจุลภาค เครื่องหมายจุลภาคบางตัวที่มีความหมายแตกต่างกันโดยสิ้นเชิง:

  • int a, b; --- รายการประกาศตัวแปรถูกคั่นด้วยเครื่องหมายจุลภาค แต่สิ่งเหล่านี้ไม่ใช่เครื่องหมายจุลภาค
  • int a=5, b=3; --- นี่คือรายการประกาศตัวแปรที่คั่นด้วยเครื่องหมายจุลภาค
  • foo(x,y)--- รายการอาร์กิวเมนต์ที่คั่นด้วยเครื่องหมายจุลภาค ในความเป็นจริงxและyสามารถประเมินได้ในลำดับใด ๆ !
  • FOO(x,y) --- รายการอาร์กิวเมนต์แมโครที่คั่นด้วยเครื่องหมายจุลภาค
  • foo<a,b> --- รายการอาร์กิวเมนต์เทมเพลตที่คั่นด้วยเครื่องหมายจุลภาค
  • int foo(int a, int b) --- รายการพารามิเตอร์คั่นด้วยเครื่องหมายจุลภาค
  • Foo::Foo() : a(5), b(3) {} --- รายการ initializer ที่คั่นด้วยเครื่องหมายจุลภาคในตัวสร้างคลาส

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

อ่านเพิ่มเติม: http://en.wikipedia.org/wiki/Comma_operator


มันมีค่าหรือไม่หากว่าoperator ,มีการบรรทุกเกินพิกัดคุณจะสูญเสียการค้ำประกันใด ๆ ในการเชื่อมโยง (เช่นเดียวกับที่คุณสูญเสียคุณสมบัติการลัดวงจรของoperator&&และoperator||หากมีการบรรทุกเกินพิกัด)
YoungJohn

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

1
เพียงความเห็นเล็กน้อยในรายการการเริ่มต้นที่คั่นด้วยเครื่องหมายจุลภาคในตัวสร้างคลาสคำสั่งจะไม่ถูกกำหนดโดยตำแหน่งในรายการ คำสั่งซื้อจะถูกกำหนดโดยตำแหน่งประกาศของชั้นเรียน เช่นstruct Foo { Foo() : a(5), b(3) {} int b; int a; }evaulates ก่อนb(3) นี้เป็นสิ่งสำคัญถ้ารายการของคุณเป็นเช่นดังนั้น:a(5) Foo() : a(5), b(a) {}b จะไม่ถูกตั้งค่าเป็น 5 แต่แทนที่จะเป็นค่าเริ่มต้นของ a ซึ่งคอมไพเลอร์ของคุณอาจหรือไม่เตือน
Jonathan Gawrych

ฉันเพิ่งเจอผู้ประกอบการจุลภาคที่มีสองลอยอยู่จุดที่ประเมินและทิ้งจำนวนคืออะไร?
Aaron Franke

ฉันไม่คิดว่าทุกคนสามารถตอบได้ คุณจะต้องแสดงมันในบริบท อาจเป็นคำถามแยกต่างหาก
CygnusX1

38

ค่าของaจะbแต่ค่าของการแสดงออกcจะเป็น นั่นคือใน

d = (a = b, c);

จะต้องมีค่าเท่ากับbและจะเท่ากับdc


19
เกือบถูกต้องแล้ว ใบแจ้งยอดไม่มีค่ามีการแสดงออก ค่าของนิพจน์นั้นคือ c
Leon Timmermans

ทำไมเรื่องนี้จึงถูกนำมาใช้แทนa = b; d = c;?
Aaron Franke

นี่ทำให้ฉันเข้าใจว่าผู้คนพูดถึงผลข้างเคียงอะไร
เชือกผูกรองเท้า


2

ค่าของ a จะเท่ากับ b เนื่องจากตัวดำเนินการเครื่องหมายจุลภาคมีลำดับความสำคัญต่ำกว่าตัวดำเนินการกำหนดค่า


2

ใช่ผู้ประกอบการจุลภาคมีความสำคัญต่ำกว่าผู้ประกอบการที่ได้รับมอบหมาย

#include<stdio.h>
int main()
{
          int i;
          i = (1,2,3);
          printf("i:%d\n",i);
          return 0;
}

เอาต์พุต: i = 3
เนื่องจากตัวดำเนินการเครื่องหมายจุลภาคส่งคืนค่าขวาสุดเสมอ
ในกรณีของผู้ประกอบการด้วยเครื่องหมายจุลภาคกับผู้ประกอบการที่ได้รับมอบหมาย:

 int main()
{
      int i;
      i = 1,2,3;
      printf("i:%d\n",i);
      return 0;
}

Ouput: i = 1
อย่างที่เรารู้ว่าเครื่องหมายจุลภาคมีความสำคัญต่ำกว่าการมอบหมาย .....


ดังนั้นตัวอย่างที่สองแตกต่างจากการมีi = 1;ในบรรทัดนั้นอย่างไร
Aaron Franke

-3

สิ่งแรกสิ่งแรก:เครื่องหมายจุลภาคไม่ใช่ตัวดำเนินการสำหรับคอมไพเลอร์มันเป็นเพียงโทเค็นที่ได้รับความหมายในบริบทกับโทเค็นอื่น ๆ

สิ่งนี้หมายความว่าอย่างไรและเพราะเหตุใด

ตัวอย่างที่ 1:

เพื่อให้เข้าใจถึงความแตกต่างระหว่างความหมายของโทเค็นเดียวกันในบริบทที่แตกต่างกันเรามาดูตัวอย่างนี้:

class Example {
   Foo<int, char*> ContentA;
}

มักจะเป็น c ++ เริ่มต้นที่จะคิดว่าการแสดงออกนี้อาจ / จะเปรียบเทียบสิ่ง แต่มันเป็นความผิด absolutly ความหมายของ<, >และ,โทเค็น depent กับบริบทของการใช้งาน

การแปลความหมายที่ถูกต้องของตัวอย่างด้านบนนั้นแน่นอนว่าเป็นการสร้างสัญชาตญาณของแม่แบบ

ตัวอย่างที่ 2:

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

for(a=5,b=0;a<42;a++,b--)
   ...

ความหมายของเครื่องหมายจุลภาคขึ้นอยู่กับบริบทของการใช้นี่คือบริบทของ forก่อสร้าง

เครื่องหมายจุลภาคในบริบทหมายถึงอะไรจริง ๆ

หากต้องการทำให้ซับซ้อนยิ่งขึ้น (เช่นใน C ++) ตัวดำเนินการคอมม่าสามารถโอเวอร์โหลดได้เอง (ขอบคุณKonrad Rudolphสำหรับการชี้ให้เห็น)

เพื่อกลับมาที่คำถามรหัส

a = b, c;

แปลว่าคอมไพเลอร์เหมือนกัน

(a = b), c;

เนื่องจากลำดับความสำคัญของ=โทเค็น / ผู้ดำเนินการสูงกว่าลำดับความสำคัญของ,โทเค็น

และสิ่งนี้ถูกตีความในบริบทเช่น

a = b;
c;

(โปรดทราบว่าการตีความขึ้นอยู่กับบริบทในที่นี้ไม่เป็นการเรียกฟังก์ชัน / วิธีหรือการสร้างเทมเพลต)


1
แน่ใจว่ามันคือบางทีฉันอาจจะใช้คำศัพท์ที่ไม่ถูกต้อง (สำหรับ lexer มันเป็นสัญญาณนั่นเอง)
Quonux

2
ในฐานะที่เป็นหนึ่งทำงานร่วมกับผู้ประกอบการ, (sic), จุลภาคเป็นผู้ดำเนินการ
DragonLord

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