เครื่องหมายจุลภาคตัวดำเนินการทำอะไร


167

สิ่งที่ไม่,ดำเนินการทำใน C?



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

2
@SergeyK - ระบุว่าสิ่งนี้ถูกถามและตอบเมื่อหลายปีก่อนมีความเป็นไปได้มากกว่าที่คำถามอื่น ๆ จะซ้ำซ้อนกับคำถามนี้ อย่างไรก็ตามแท็กอื่น ๆ ยังมีการติดแท็กแบบคู่ด้วยทั้งcและc ++ซึ่งเป็นสิ่งที่สร้างความรำคาญ นี่คือคำถาม & คำตอบ C-only พร้อมคำตอบที่ดี
Jonathan Leffler

คำตอบ:


129

การแสดงออก:

(expression1,  expression2)

นิพจน์แรก 1 ถูกประเมินจากนั้นนิพจน์ 2 จะถูกประเมินค่าและส่งกลับค่าของนิพจน์ 2 สำหรับนิพจน์ทั้งหมด


3
ถ้าฉันเขียน i = (5,4,3,2,1,0) ถ้าอย่างนั้นมันควรจะคืนค่า 0 ถูกต้องไหม แต่ฉันจะถูกกำหนดค่า 5? คุณกรุณาช่วยฉันเข้าใจว่าฉันกำลังไปไหนผิด
Jayesh

19
@James: ค่าของการดำเนินการด้วยเครื่องหมายจุลภาคจะเป็นค่าของนิพจน์สุดท้ายเสมอ ไม่มีจุดใดที่จะiมีค่า 5, 4, 3, 2 หรือ 1 มันเป็นเพียง 0 มันไม่มีประโยชน์จริงยกเว้นว่าการแสดงออกมีผลข้างเคียง
Jeff Mercado

6
โปรดทราบว่ามีจุดลำดับเต็มรูปแบบระหว่างการประเมิน LHS ของการแสดงออกของเครื่องหมายจุลภาคและการประเมินผลของ RHS (ดูคำตอบของShafik Yaghmourสำหรับการอ้างอิงจากมาตรฐาน C99) นี่เป็นคุณสมบัติที่สำคัญของโอเปอเรเตอร์คอมม่า
Jonathan Leffler

5
i = b, c;เทียบเท่ากับ(i = b), cเพราะเนื่องจากได้รับมอบหมายมีความสำคัญสูงกว่าผู้ประกอบการจุลภาค= ,ตัวดำเนินการเครื่องหมายจุลภาคมีลำดับความสำคัญต่ำสุดของทั้งหมด
cyclaminist

1
ฉันกังวลว่าเครื่องหมายวงเล็บจะทำให้เข้าใจผิดในสองครั้ง: (1) ไม่จำเป็น - ผู้ใช้เครื่องหมายจุลภาคไม่จำเป็นต้องล้อมรอบด้วยวงเล็บ และ (2) อาจสับสนกับวงเล็บในรายการอาร์กิวเมนต์ของการเรียกใช้ฟังก์ชัน - แต่เครื่องหมายจุลภาคในรายการอาร์กิวเมนต์ไม่ใช่เครื่องหมายจุลภาค อย่างไรก็ตามการแก้ไขไม่ใช่เรื่องเล็กน้อย อาจจะ: ในคำสั่ง: การประเมินexpression1, expression2;ครั้งแรกน่าexpression1จะเป็นผลข้างเคียงของมัน (เช่นการเรียกใช้ฟังก์ชั่น) จากนั้นก็มีจุดลำดับจากนั้นexpression2จะมีการประเมินและมูลค่ากลับมา ...
Jonathan Leffler

119

ฉันเคยเห็นใช้มากที่สุดในwhileลูป:

string s;
while(read_string(s), s.len() > 5)
{
   //do something
}

มันจะทำการดำเนินการจากนั้นทำการทดสอบตามผลข้างเคียง วิธีอื่นจะทำเช่นนี้:

string s;
read_string(s);
while(s.len() > 5)
{
   //do something
   read_string(s);
}

21
เฮ้นั่นมันดีมาก! ฉันมักจะต้องทำสิ่งนอกรีตนอกลู่นอกทางเพื่อแก้ไขปัญหานั้น
staticsan

6
แม้ว่ามันอาจจะคลุมเครือน้อยลงและอ่านง่ายขึ้นถ้าคุณทำสิ่งที่ชอบ: while (read_string(s) && s.len() > 5). เห็นได้ชัดว่าจะไม่ทำงานหากread_stringไม่มีค่าตอบแทน (หรือไม่มีค่าที่มีความหมาย) (แก้ไข: ขออภัยไม่ได้สังเกตว่าโพสต์นี้เก่าแล้ว)
jamesdlin

11
@staticsan อย่ากลัวที่จะใช้while (1)กับbreak;ข้อความในร่างกาย การพยายามบังคับให้ส่วนที่แบ่งออกของรหัสเข้าสู่การทดสอบในขณะที่หรือลงในการทดสอบแบบทำในขณะที่มักจะเสียพลังงานและทำให้รหัสยากที่จะเข้าใจ
potrzebie

8
@jamesdlin ... และผู้คนยังคงอ่านมัน หากคุณมีสิ่งที่มีประโยชน์ที่จะพูดก็ให้พูดออกมา ฟอรัมมีปัญหากับเธรดที่ถูกกู้คืนเนื่องจากเธรดมักจะเรียงตามวันที่โพสต์ล่าสุด StackOverflow ไม่มีปัญหาดังกล่าว
Dimitar Slavchev

3
@potrzebie ฉันชอบวิธีจุลภาคดีกว่าwhile(1)และbreak;
Michael

38

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

void rev(char *s, size_t len)
{
  char *first;
  for ( first = s, s += len - 1; s >= first; --s)
      /*^^^^^^^^^^^^^^^^^^^^^^^*/ 
      putchar(*s);
}

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

จากมาตรฐาน C99 ฉบับร่างไวยากรณ์จะเป็นดังนี้:

expression:
  assignment-expression
  expression , assignment-expression

และวรรค 2พูดว่า:

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

Footnote 97พูดว่า:

ผู้ประกอบการจุลภาคไม่ได้ผลผลิต lvalue

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

สิ่งสำคัญคือให้สังเกตว่าโอเปอเรเตอร์จุลภาคมีลำดับความสำคัญต่ำสุดและดังนั้นจึงมีกรณีที่การใช้()สามารถสร้างความแตกต่างใหญ่เช่น:

#include <stdio.h>

int main()
{
    int x, y ;

    x = 1, 2 ;
    y = (3,4) ;

    printf( "%d %d\n", x, y ) ;
}

จะมีผลลัพธ์ต่อไปนี้:

1 4

28

ตัวดำเนินการเครื่องหมายจุลภาครวมสองนิพจน์ทั้งสองข้างไว้ในที่เดียวโดยประเมินทั้งคู่ตามลำดับจากซ้ายไปขวา ค่าของด้านขวาถูกส่งคืนเป็นค่าของนิพจน์ทั้งหมด (expr1, expr2)เป็นเหมือน{ expr1; expr2; }แต่คุณสามารถใช้ผลลัพธ์ของexpr2การโทรหรือการกำหนดฟังก์ชัน

มันมักจะเห็นในforลูปเพื่อเริ่มต้นหรือรักษาหลายตัวแปรเช่นนี้

for (low = 0, high = MAXSIZE; low < high; low = newlow, high = newhigh)
{
    /* do something with low and high and put new values
       in newlow and newhigh */
}

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

unsigned char outbuff[BUFFSIZE];
unsigned char *ptr = outbuff;

*ptr++ = first_byte_value;
*ptr++ = second_byte_value;

send_buff(outbuff, (int)(ptr - outbuff));

ไหนค่าได้shortหรือints เราทำอย่างนี้:

*((short *)ptr)++ = short_value;
*((int *)ptr)++ = int_value;

ต่อมาเราอ่านว่านี่ไม่ถูกต้อง C จริง ๆ เพราะ(short *)ptrไม่มีค่า l อีกต่อไปและไม่สามารถเพิ่มขึ้นได้แม้ว่าคอมไพเลอร์ของเราในเวลานั้นไม่สนใจ ในการแก้ไขปัญหานี้เราแบ่งนิพจน์เป็นสองส่วน:

*(short *)ptr = short_value;
ptr += sizeof(short);

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

#define ASSIGN_INCR(p, val, type)  ((*((type) *)(p) = (val)), (p) += sizeof(type))

ด้วยการใช้ตัวดำเนินการคอมม่าเราสามารถใช้สิ่งนี้ในการแสดงออกหรือเป็นคำสั่งตามที่เราต้องการ:

if (need_to_output_short)
    ASSIGN_INCR(ptr, short_value, short);

latest_pos = ASSIGN_INCR(ptr, int_value, int);

send_buff(outbuff, (int)(ASSIGN_INCR(ptr, last_value, int) - outbuff));

ฉันไม่ได้แนะนำตัวอย่างใด ๆ เหล่านี้ว่าเป็นสไตล์ที่ดี! อันที่จริงฉันดูเหมือนจะจำรหัสที่สมบูรณ์ของ Steve McConnell เพื่อให้คำแนะนำแม้กระทั่งการใช้เครื่องหมายคอมม่าในforลูป: เพื่อความสะดวกในการอ่านและการบำรุงรักษาลูปควรถูกควบคุมโดยตัวแปรเพียงตัวเดียวเท่านั้นและนิพจน์ในforบรรทัดนั้น ไม่ใช่บิตพิเศษอื่น ๆ ของการเริ่มต้นหรือการบำรุงรักษาลูป


ขอบคุณ! มันเป็นคำตอบแรกของฉันใน StackOverflow: ตั้งแต่นั้นมาฉันอาจได้เรียนรู้ว่าความรัดกุมจะต้องมีมูลค่า :-)
Paul Stephenson

บางครั้งฉันเห็นคุณค่าของการใช้คำฟุ่มเฟื่อยในกรณีที่คุณอธิบายวิวัฒนาการของการแก้ปัญหา
diod สีเขียว

8

มันทำให้การประเมินผลของหลายงบ แต่ใช้เพียงหนึ่งล่าสุดเป็นค่าผลลัพธ์ (rvalue ฉันคิดว่า)

ดังนั้น...

int f() { return 7; }
int g() { return 8; }

int x = (printf("assigning x"), f(), g() );

ควรส่งผลให้ x ถูกตั้งค่าเป็น 8


3

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

for (tmp=0, i = MAX; i > 0; i--)

2

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

bool arraysAreMirrored(int a1[], int a2[], size_t size)
{
  size_t i1, i2;
  for(i1 = 0, i2 = size - 1; i1 < size; i1++, i2--)
  {
    if(a1[i1] != a2[i2])
    {
      return false;
    }
  }

  return true;
}

ให้อภัยฉันหากมีข้อผิดพลาดทางไวยากรณ์หรือถ้าฉันผสมในสิ่งที่ไม่เข้มงวด C. ฉันไม่เถียงว่าตัวดำเนินการเป็นรูปแบบที่ดี แต่นั่นคือสิ่งที่คุณสามารถใช้เพื่อ ในกรณีข้างต้นฉันอาจใช้การwhileวนซ้ำแทนดังนั้นการแสดงออกหลายครั้งในการเริ่มต้นและการวนซ้ำจะชัดเจนมากขึ้น (และฉันจะเริ่มต้น i1 และ i2 แบบอินไลน์แทนการประกาศแล้วเริ่มต้น .... blah blah blah.)


ฉันเข้าใจคุณหมายถึง i1 = 0 = i2 ขนาด -1
frankster

-2

ฉันขอคืนสิ่งนี้เพื่อตอบคำถามจาก @Rajesh และ @JeffMercado ซึ่งฉันคิดว่ามีความสำคัญมากเนื่องจากนี่เป็นหนึ่งในโปรแกรมค้นหายอดนิยม

ยกตัวอย่างโค้ดต่อไปนี้

int i = (5,4,3,2,1);
int j;
j = 5,4,3,2,1;
printf("%d %d\n", i , j);

มันจะพิมพ์

1 5

iกรณีที่มีการจัดการตามที่อธิบายไว้โดยคำตอบมากที่สุด นิพจน์ทั้งหมดจะได้รับการประเมินตามลำดับจากซ้ายไปขวา แต่จะกำหนดเฉพาะรายการสุดท้ายiเท่านั้น ผลลัพธ์ของ( นิพจน์ ) is1`

jกรณีตามกฎความสำคัญที่แตกต่างกันตั้งแต่,มีความสำคัญผู้ประกอบการต่ำสุด เพราะกฎเหล่านั้นคอมไพเลอร์เห็นมอบหมายแสดงออกคงที่อย่างต่อเนื่อง ... การแสดงออกที่มีการประเมินอีกครั้งในซ้ายไปขวาการสั่งซื้อและผลข้างเคียงของพวกเขาอยู่ที่มองเห็นจึงjเป็นเป็นผลมาจาก5j = 5

Interstingly, int j = 5,4,3,2,1;ไม่ได้รับอนุญาตตามภาษาข้อมูลจำเพาะ initializerคาดว่าจะได้รับมอบหมายแสดงออกเพื่อให้ตรง,ผู้ประกอบการไม่ได้รับอนุญาต

หวังว่านี่จะช่วยได้

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