Co robi operator przecinka?

167

Co robi ,operator w C?

lillq
źródło
1
Jak zauważyłem w mojej odpowiedzi, po ocenie lewego operandu znajduje się punkt sekwencji. W przeciwieństwie do przecinka w wywołaniu funkcji, które jest po prostu gramatyczne.
Shafik Yaghmour,
2
@SergeyK. - Biorąc pod uwagę, że zadawano to wiele lat wcześniej i udzielano na nie odpowiedzi, bardziej prawdopodobne jest, że drugie jest powtórzeniem tego pytania. Jednak drugi jest również podwójnie oznaczony zarówno c, jak i c ++ , co jest uciążliwe. To jest pytanie i odpowiedzi tylko dla C, z przyzwoitymi odpowiedziami.
Jonathan Leffler

Odpowiedzi:

129

Ekspresja:

(expression1,  expression2)

Obliczane jest najpierw wyrażenie1, następnie wyrażenie2, a wartość wyrażenia2 jest zwracana dla całego wyrażenia.

lillq
źródło
3
to jeśli napiszę i = (5,4,3,2,1,0) to idealnie powinno zwrócić 0, prawda? ale jest przypisywana wartość 5? Czy możesz mi pomóc zrozumieć, do czego zmierzam źle?
Jayesh
19
@James: Wartość operacji na przecinku zawsze będzie wartością ostatniego wyrażenia. W żadnym momencie nie będzie imiał wartości 5, 4, 3, 2 ani 1. Jest to po prostu 0. Jest praktycznie bezużyteczne, chyba że wyrażenia mają skutki uboczne.
Jeff Mercado
6
Zauważ, że nie jest w pełni punkt sekwencja pomiędzy oceną LHS o wyrażeniu przecinkiem i oceny RHS (patrz Shafik Yaghmour jest odpowiedź na ofertę ze standardu C99). To ważna właściwość operatora przecinka.
Jonathan Leffler
5
i = b, c;jest równoważne z (i = b), cponieważ przypisanie =ma wyższy priorytet niż operator przecinka ,. Operator przecinka ma najniższy priorytet ze wszystkich.
cyklaminista
1
Obawiam się, że nawiasy wprowadzają w błąd z dwóch powodów: (1) nie są potrzebne - operator przecinka nie musi być otoczony nawiasami; i (2) można by je pomylić z nawiasami wokół listy argumentów wywołania funkcji - ale przecinek na liście argumentów nie jest operatorem przecinka. Jednak naprawienie tego nie jest całkowicie trywialne. Może: w stwierdzeniu: expression1, expression2;najpierw expression1jest oceniany, przypuszczalnie pod kątem skutków ubocznych (takich jak wywołanie funkcji), potem jest punkt sekwencji, następnie expression2jest oceniany i zwracana wartość…
Jonathan Leffler
119

Widziałem najczęściej używane w whilepętlach:

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

Wykona operację, a następnie wykona test oparty na efekcie ubocznym. Innym sposobem byłoby zrobienie tego w ten sposób:

string s;
read_string(s);
while(s.len() > 5)
{
   //do something
   read_string(s);
}
crashmstr
źródło
21
Hej, to fajne! Często musiałem robić nieortodoksyjne rzeczy w pętli, aby rozwiązać ten problem.
staticsan
6
Mimo, że to prawdopodobnie mniej niejasne i bardziej czytelny, jeśli zrobił coś takiego: while (read_string(s) && s.len() > 5). Oczywiście to nie zadziała, jeśli read_stringnie ma wartości zwracanej (lub nie ma sensownej wartości). (Edycja: Przepraszamy, nie zauważyłem, ile lat miał ten post.)
jamesdlin
11
@staticsan Nie bój się używać while (1)z break;oświadczeniem w treści. Próba wymuszenia przełamania fragmentu kodu w trakcie testu while lub w dół do testu do-while jest często stratą energii i sprawia, że ​​kod jest trudniejszy do zrozumienia.
potrzebabie
8
@jamesdlin ... a ludzie nadal to czytają. Jeśli masz coś pożytecznego do powiedzenia, powiedz to. Fora mają problemy z wskrzeszonymi wątkami, ponieważ wątki są zwykle sortowane według daty ostatniego postu. StackOverflow nie ma takich problemów.
Dimitar Slavchev
3
@potrzebie Podejście z przecinkiem podoba mi się znacznie lepiej niż while(1)i break;
Michael
38

Operator przecinek oceni lewy argument, odrzucić wynik, a następnie ocenić prawy operand i że będzie wynik. Idiomatycznym zastosowanie, jak wspomniano w łączu przy inicjalizacji zmiennych stosowanych w forpętlę i daje następujący przykład:

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

W przeciwnym razie nie ma wielu świetnych zastosowań operatora przecinka , chociaż łatwo jest nadużywać generowania kodu, który jest trudny do odczytania i utrzymania.

Z wersji roboczej standardu C99 gramatyka wygląda następująco:

expression:
  assignment-expression
  expression , assignment-expression

a ust. 2 mówi:

Lewy argument operatora przecinek oceniano jako pusta ekspresji; po jego ocenie znajduje się punkt sekwencji. Następnie oceniany jest prawy operand; wynik ma swój typ i wartość. 97) Jeśli zostanie podjęta próba zmodyfikowania wyniku operatora przecinka lub uzyskania do niego dostępu po następnym punkcie sekwencji, zachowanie jest niezdefiniowane.

Przypis 97 mówi:

Operator przecinka nie daje lwartości .

co oznacza, że ​​nie możesz przypisać do wyniku operatora przecinka .

Należy zauważyć, że operator przecinka ma najniższy priorytet i dlatego istnieją przypadki, w których użycie ()może spowodować dużą różnicę, na przykład:

#include <stdio.h>

int main()
{
    int x, y ;

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

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

będzie miał następujący wynik:

1 4
Shafik Yaghmour
źródło
28

Operator przecinka łączy dwa wyrażenia po obu stronach w jedno, oceniając je w kolejności od lewej do prawej. Wartość po prawej stronie jest zwracana jako wartość całego wyrażenia. (expr1, expr2)jest jak, { expr1; expr2; }ale możesz użyć wyniku expr2w wywołaniu funkcji lub przypisaniu.

Często występuje w forpętlach w celu zainicjowania lub utrzymania wielu zmiennych, jak poniżej:

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 */
}

Poza tym użyłem go tylko "w gniewie" w jednym innym przypadku, kiedy pakowałem dwie operacje, które zawsze powinny iść razem w makro. Mieliśmy kod, który kopiował różne wartości binarne do bufora bajtów w celu wysłania w sieci, a wskaźnik utrzymywał się w miejscu, w którym doszliśmy do:

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

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

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

Tam, gdzie wartości były shorts lub ints, zrobiliśmy to:

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

Później przeczytaliśmy, że nie było to naprawdę poprawne C, ponieważ (short *)ptrnie jest już wartością l i nie może być zwiększane, chociaż nasz kompilator w tamtym czasie nie miał nic przeciwko. Aby to naprawić, podzieliliśmy wyrażenie na dwie części:

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

Jednak to podejście polegało na tym, że wszyscy deweloperzy pamiętali o umieszczaniu obu instrukcji przez cały czas. Chcieliśmy funkcji, w której można by przekazać wskaźnik wyjściowy, wartość i typ wartości. Ponieważ jest to C, a nie C ++ z szablonami, nie moglibyśmy mieć funkcji, która przyjmuje dowolny typ, więc zdecydowaliśmy się na makro:

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

Korzystając z operatora przecinka, mogliśmy użyć tego w wyrażeniach lub jako oświadczenia, jak chcieliśmy:

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));

Nie sugeruję, że którykolwiek z tych przykładów jest dobry! Rzeczywiście, wydaje mi się, że pamiętam Code Complete Steve'a McConnell'a, który odradzał nawet używanie operatorów przecinków w forpętli: dla czytelności i łatwości utrzymania pętla powinna być kontrolowana tylko przez jedną zmienną, a wyrażenia w forsamej linii powinny zawierać tylko kod sterujący pętlą, a nie inne dodatkowe bity inicjalizacji lub utrzymania pętli.

Paul Stephenson
źródło
Dzięki! To była moja pierwsza odpowiedź na StackOverflow: od tamtej pory chyba nauczyłem się, że lapidarność ma znaczenie :-).
Paul Stephenson,
Czasami cenię sobie trochę gadatliwości, jak ma to miejsce w tym przypadku, gdy opisujesz ewolucję rozwiązania (jak się tam dostałeś).
zielona dioda
8

Powoduje ocenę wielu instrukcji, ale wykorzystuje tylko ostatnią jako wartość wynikową (chyba rvalue).

Więc...

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

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

powinno skutkować ustawieniem x na 8.

Ben Collins
źródło
3

Jak wskazywały wcześniejsze odpowiedzi, program ocenia wszystkie stwierdzenia, ale używa ostatniego jako wartości wyrażenia. Osobiście uważam, że jest to przydatne tylko w wyrażeniach pętli:

for (tmp=0, i = MAX; i > 0; i--)
DGentry
źródło
2

Jedynym miejscem, w którym widziałem, że jest przydatny, jest pisanie funkowej pętli, w której chcesz zrobić wiele rzeczy w jednym z wyrażeń (prawdopodobnie wyrażeniu init lub wyrażeniu pętli. Coś w rodzaju:

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

Przepraszam, jeśli są jakieś błędy składniowe lub jeśli dodałem coś, co nie jest ścisłe C. Nie twierdzę, że operator, jest dobrą formą, ale do tego można go użyć. W powyższym przypadku prawdopodobnie użyłbym whilepętli, aby wiele wyrażeń na init i loop było bardziej oczywiste. (I zainicjowałbym i1 i i2 inline zamiast deklarować, a następnie inicjalizować ... bla bla bla.)

piekarnik
źródło
Zakładam, że masz na myśli i1 = 0, i2 = rozmiar -1
frankster
-2

Wracam do tego po prostu, aby odpowiedzieć na pytania @Rajesh i @JeffMercado, które moim zdaniem są bardzo ważne, ponieważ jest to jedno z najpopularniejszych haseł wyszukiwarek.

Weźmy na przykład następujący fragment kodu

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

To się wydrukuje

1 5

iPrzypadek jest traktowany jako wyjaśnione przez większość odpowiedzi. Wszystkie wyrażenia są obliczane w kolejności od lewej do prawej, ale tylko ostatnie jest przypisane i. Wynik ( wyrażenia ) is1`.

jPrzypadek następująco różne zasady pierwszeństwa, ponieważ ,ma najniższy priorytet operatora. Z powodu tych przepisów, kompilator widzi przypisania ekspresję Constant Constant ... . Wyrażenia są ponownie oceniane w kolejności od lewej do prawej, a ich skutki uboczne pozostają widoczne, dlatego też jjest 5wynikiem j = 5.

Co ciekawe, int j = 5,4,3,2,1;specyfikacja języka nie zezwala. Inicjator oczekuje zadanie ekspresję więc bezpośredni ,operator nie jest dozwolone.

Mam nadzieję że to pomoże.

ViNi89
źródło