Was macht der Kommaoperator?

167

Was macht der ,Operator in C?

lillq
quelle
Mögliches Duplikat von Was ist die richtige Verwendung des Kommaoperators?
Sergey K.
1
Wie ich in meiner Antwort feststelle, gibt es nach der Auswertung des linken Operanden einen Sequenzpunkt. Dies ist anders als das Komma in einem Funktionsaufruf, der nur grammatikalisch ist.
Shafik Yaghmour
2
@ SergeyK. - Da dies Jahre vor dem anderen gestellt und beantwortet wurde, ist es wahrscheinlicher, dass das andere ein Duplikat dieser Frage ist. Der andere ist jedoch auch mit c und c ++ doppelt markiert , was ein Ärgernis ist. Dies ist eine reine C & A-Frage mit anständigen Antworten.
Jonathan Leffler

Antworten:

129

Der Ausdruck:

(expression1,  expression2)

Zuerst wird Ausdruck1 ausgewertet, dann wird Ausdruck2 ausgewertet und der Wert von Ausdruck2 wird für den gesamten Ausdruck zurückgegeben.

lillq
quelle
3
Wenn ich dann i = (5,4,3,2,1,0) schreibe, sollte es idealerweise 0 zurückgeben, richtig? aber mir wird ein Wert von 5 zugewiesen? Können Sie mir bitte helfen zu verstehen, wo ich falsch liege?
Jayesh
19
@James: Der Wert einer Kommaoperation ist immer der Wert des letzten Ausdrucks. Zu keinem Zeitpunkt werden idie Werte 5, 4, 3, 2 oder 1 angegeben. Es ist einfach 0. Es ist praktisch nutzlos, es sei denn, die Ausdrücke haben Nebenwirkungen.
Jeff Mercado
6
Beachten Sie, dass es ein vollständiger Sequenzpunkt zwischen der Bewertung der LHS des Komma - Ausdrucks und der Auswertung der RHS (siehe Shafik Yaghmour ‚s Antwort für ein Zitat aus dem C99 - Standard). Dies ist eine wichtige Eigenschaft des Kommaoperators.
Jonathan Leffler
5
i = b, c;ist äquivalent zu (i = b), cweil, weil die Zuweisung =eine höhere Priorität hat als der Kommaoperator ,. Der Kommaoperator hat die niedrigste Priorität von allen.
Cyclaminist
1
Ich mache mir Sorgen, dass die Klammern in zweierlei Hinsicht irreführend sind: (1) Sie sind nicht erforderlich - der Kommaoperator muss nicht von Klammern umgeben sein. und (2) sie könnten mit den Klammern um die Argumentliste eines Funktionsaufrufs verwechselt werden - aber das Komma in der Argumentliste ist nicht der Kommaoperator. Die Behebung ist jedoch nicht ganz trivial. Vielleicht: In der Aussage: wird expression1, expression2;zuerst expression1ausgewertet, vermutlich auf seine Nebenwirkungen (wie das Aufrufen einer Funktion), dann gibt es einen Sequenzpunkt, dann expression2wird ausgewertet und der Wert zurückgegeben…
Jonathan Leffler
119

Ich habe am häufigsten in whileSchleifen verwendet gesehen:

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

Es wird die Operation ausführen und dann einen Test basierend auf einer Nebenwirkung durchführen. Der andere Weg wäre, es so zu machen:

string s;
read_string(s);
while(s.len() > 5)
{
   //do something
   read_string(s);
}
Crashmstr
quelle
21
Hey, das ist schick! Ich musste oft unorthodoxe Dinge in einer Schleife tun, um dieses Problem zu beheben.
Statik
6
Obwohl es wahrscheinlich weniger dunkel und lesbarer wäre, wenn Sie so etwas tun würden : while (read_string(s) && s.len() > 5). Offensichtlich würde das nicht funktionieren, wenn read_stringes keinen Rückgabewert gibt (oder keinen aussagekräftigen). (Edit: Sorry, habe nicht bemerkt, wie alt dieser Beitrag war.)
Jamesdlin
11
@staticsan Hab keine Angst, while (1)mit einer break;Aussage im Körper zu verwenden. Der Versuch, den ausbrechenden Teil des Codes in den while-Test oder in den do-while-Test zu zwingen, ist oft eine Energieverschwendung und erschwert das Verständnis des Codes.
Potrzebie
8
@ Jamesdlin ... und die Leute lesen es immer noch. Wenn Sie etwas Nützliches zu sagen haben, dann sagen Sie es. Foren haben Probleme mit wiederbelebten Threads, da Threads normalerweise nach dem Datum des letzten Beitrags sortiert sind. StackOverflow hat solche Probleme nicht.
Dimitar Slavchev
3
@potrzebie Ich mag den Komma-Ansatz viel besser als while(1)und break;
Michael
38

Der Kommaoperator wertet den linken Operanden aus, verwirft das Ergebnis und wertet dann den rechten Operanden aus, und das ist das Ergebnis. Die im Link angegebene idiomatische Verwendung erfolgt beim Initialisieren der in einer forSchleife verwendeten Variablen und gibt das folgende Beispiel:

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

Ansonsten gibt es nicht viele großartige Verwendungsmöglichkeiten für den Kommaoperator , obwohl es leicht zu missbrauchen ist, Code zu generieren, der schwer zu lesen und zu warten ist.

Nach dem Entwurf des C99-Standards lautet die Grammatik wie folgt:

expression:
  assignment-expression
  expression , assignment-expression

und Absatz 2 sagt:

Der linke Operand eines Kommaoperators wird als void-Ausdruck ausgewertet. Nach seiner Auswertung gibt es einen Sequenzpunkt. Dann wird der richtige Operand ausgewertet; Das Ergebnis hat seinen Typ und Wert. 97) Wenn versucht wird, das Ergebnis eines Kommaoperators zu ändern oder nach dem nächsten Sequenzpunkt darauf zuzugreifen, ist das Verhalten undefiniert.

In Fußnote 97 heißt es:

Ein Kommaoperator liefert keinen l-Wert .

Dies bedeutet, dass Sie dem Ergebnis des Kommaoperators keine Zuordnung zuweisen können .

Es ist wichtig zu beachten, dass der Kommaoperator die niedrigste Priorität hat. Daher gibt es Fälle, in denen die Verwendung ()einen großen Unterschied machen kann, zum Beispiel:

#include <stdio.h>

int main()
{
    int x, y ;

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

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

wird die folgende Ausgabe haben:

1 4
Shafik Yaghmour
quelle
28

Der Kommaoperator kombiniert die beiden Ausdrücke auf beiden Seiten zu einem und wertet beide in der Reihenfolge von links nach rechts aus. Der Wert der rechten Seite wird als Wert des gesamten Ausdrucks zurückgegeben. (expr1, expr2)ist wie, { expr1; expr2; }aber Sie können das Ergebnis von expr2in einem Funktionsaufruf oder einer Zuweisung verwenden.

In forSchleifen wird häufig festgestellt, dass mehrere Variablen wie folgt initialisiert oder verwaltet werden:

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

Abgesehen davon habe ich es nur "in Wut" in einem anderen Fall verwendet, wenn zwei Operationen zusammengefasst wurden, die immer in einem Makro zusammenpassen sollten. Wir hatten Code, der verschiedene Binärwerte in einen Bytepuffer zum Senden in ein Netzwerk kopierte, und einen Zeiger, der dort beibehalten wurde, wo wir waren:

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

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

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

Wo die Werte shorts oder ints waren, haben wir dies getan:

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

Später lesen wir, dass dies kein wirklich gültiges C war, da (short *)ptres kein l-Wert mehr ist und nicht inkrementiert werden kann, obwohl es unserem damaligen Compiler nichts ausmachte. Um dies zu beheben, teilen wir den Ausdruck in zwei Teile:

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

Dieser Ansatz beruhte jedoch darauf, dass alle Entwickler daran dachten, beide Anweisungen ständig einzugeben. Wir wollten eine Funktion, mit der Sie den Ausgabezeiger, den Wert und den Werttyp übergeben können. Da dies C und nicht C ++ mit Vorlagen ist, konnte keine Funktion einen beliebigen Typ annehmen, daher haben wir uns für ein Makro entschieden:

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

Mit dem Komma-Operator konnten wir dies in Ausdrücken oder als Anweisungen verwenden, wie wir wollten:

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

Ich schlage nicht vor, dass eines dieser Beispiele einen guten Stil hat! In der Tat erinnere ich mich an Steve McConnells Code Complete, der davon abgeraten hat, Kommaoperatoren in einer forSchleife zu verwenden: Aus Gründen der Lesbarkeit und Wartbarkeit sollte die Schleife nur von einer Variablen gesteuert werden, und die Ausdrücke in der forZeile selbst sollten nur Code für die Schleifensteuerung enthalten. keine anderen zusätzlichen Teile der Initialisierung oder Schleifenwartung.

Paul Stephenson
quelle
Vielen Dank! Es war meine erste Antwort auf StackOverflow: Seitdem habe ich vielleicht gelernt, dass Prägnanz zu schätzen ist :-).
Paul Stephenson
Manchmal schätze ich ein bisschen Ausführlichkeit, wie es hier der Fall ist, wo Sie die Entwicklung einer Lösung beschreiben (wie Sie dorthin gekommen sind).
grüne Diode
8

Es bewirkt die Auswertung mehrerer Anweisungen, verwendet jedoch nur die letzte als resultierenden Wert (rvalue, glaube ich).

So...

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

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

sollte dazu führen, dass x auf 8 gesetzt wird.

Ben Collins
quelle
3

Wie in früheren Antworten angegeben, werden alle Anweisungen ausgewertet, die letzte wird jedoch als Wert des Ausdrucks verwendet. Persönlich fand ich es nur in Schleifenausdrücken nützlich:

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

Der einzige Ort, an dem ich gesehen habe, dass es nützlich ist, ist, wenn Sie eine funky Schleife schreiben, in der Sie mehrere Dinge in einem der Ausdrücke ausführen möchten (wahrscheinlich den Init-Ausdruck oder den Schleifenausdruck. So etwas wie:

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

Verzeihen Sie mir, wenn es Syntaxfehler gibt oder wenn ich etwas eingemischt habe, das nicht streng C ist. Ich behaupte nicht, dass der Operator in guter Form ist, aber dafür könnten Sie ihn verwenden. Im obigen Fall würde ich wahrscheinlich whilestattdessen eine Schleife verwenden, damit die mehrfachen Ausdrücke auf init und loop offensichtlicher sind. (Und ich würde i1 und i2 inline initialisieren, anstatt zu deklarieren und dann zu initialisieren ... bla bla bla.)

Owen
quelle
Ich nehme an, Sie meinen i1 = 0, i2 = Größe -1
Frankster
-2

Ich belebe dies einfach, um Fragen von @Rajesh und @JeffMercado zu beantworten, die ich für sehr wichtig halte, da dies einer der Top-Suchmaschinen-Hits ist.

Nehmen Sie zum Beispiel den folgenden Codeausschnitt

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

Es wird gedruckt

1 5

Der iFall wird wie in den meisten Antworten erläutert behandelt. Alle Ausdrücke werden in der Reihenfolge von links nach rechts ausgewertet, aber nur der letzte wird zugewiesen i. Das Ergebnis des ( Ausdrucks ) is1`.

Der jFall folgt unterschiedlichen Prioritätsregeln, da er ,die niedrigste Operatorpriorität hat. Aufgrund dieser Regeln sieht der Compiler Zuweisungsausdruck, konstant, konstant ... . Die Ausdrücke werden wieder in von links nach rechts ausgewertet und ihre Nebenwirkungen bleiben sichtbar, daher jist 5als Ergebnis j = 5.

Interessanterweise int j = 5,4,3,2,1;ist die Sprachspezifikation nicht zulässig. Ein Initialisierer erwartet einen Zuweisungsausdruck, sodass ein direkter ,Operator nicht zulässig ist.

Hoffe das hilft.

ViNi89
quelle