Wie funktioniert der Komma-Operator?

175

Wie funktioniert der Kommaoperator in C ++?

Zum Beispiel, wenn ich das tue:

a = b, c;  

Entspricht a b oder c?

(Ja, ich weiß, dass dies einfach zu testen ist. Dokumentieren Sie hier einfach, damit jemand die Antwort schnell findet.)

Update: Diese Frage hat bei Verwendung des Komma-Operators eine Nuance aufgedeckt. Nur um dies zu dokumentieren:

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

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

Diese Frage wurde tatsächlich von einem Tippfehler im Code inspiriert. Was sein sollte

a = b;
c = d;

Wurde zu

a = b,    //  <-  Note comma typo!
c = d;
Joe Schneider
quelle
Lesen Sie hier mehr darüber. stackoverflow.com/questions/12824378/…
Coding Mash
1
Mögliches Duplikat von Was macht der Kommaoperator `,` in C? . Es hat dich um einen Tag geschlagen. Und die Antwort von lillq gibt eine Antwort auf die Frage nach a = (b, c);.
JWW
5
Aber in diesem Fall a = b, c = d;tatsächlich die gleiche Leistung wie beabsichtigt a = b; c = d;?
Bondolin
@ NargothBond Nicht unbedingt. Wenn bund dFunktionsbewertungen sind, die einen gemeinsamen Zustand verwenden (und ändern), wird die Ausführungsreihenfolge erst definiert C++17.
Nyronium

Antworten:

74

Es wäre gleich b.

Der Kommaoperator hat eine niedrigere Priorität als die Zuweisung.

Leon Timmermans
quelle
129

Beachten Sie, dass der Komma-Operator in C ++ möglicherweise überladen ist. Das tatsächliche Verhalten kann daher sehr unterschiedlich zu dem erwarteten sein.

Beispielsweise verwendet Boost.Spirit den Komma-Operator sehr geschickt, um Listeninitialisierer für Symboltabellen zu implementieren. Somit ist die folgende Syntax möglich und sinnvoll:

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

Beachten Sie, dass der Code aufgrund der Priorität des Operators (absichtlich!) Identisch mit ist

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

Das heißt, der erste aufgerufene Operator keywords.operator =("and")gibt ein Proxy-Objekt zurück, für das die verbleibenden operator,s aufgerufen werden:

keywords.operator =("and").operator ,("or").operator ,("not").operator ,("xor");
Konrad Rudolph
quelle
Ähm, Sie können die Priorität jedoch nicht ändern, was bedeutet, dass Sie wahrscheinlich Klammern um Ihre Liste setzen sollten.
Jeff Burdges
18
@ Jeff Im Gegenteil. Mit einer Klammer um die Liste würde dies nicht funktionieren, da der Compiler nur den Kommaoperator zwischen zwei sieht char[], der nicht überladen werden kann. Der Code ruft absichtlich zuerst das operator=und anschließend operator,für jedes verbleibende Element auf.
Konrad Rudolph
125

Der Kommaoperator hat die niedrigste Priorität aller C / C ++ - Operatoren. Daher ist es immer der letzte, der an einen Ausdruck bindet, was Folgendes bedeutet:

a = b, c;

ist äquivalent zu:

(a = b), c;

Eine weitere interessante Tatsache ist, dass der Kommaoperator einen Sequenzpunkt einführt . Dies bedeutet, dass der Ausdruck:

a+b, c(), d

Es wird garantiert, dass die drei Unterausdrücke ( a + b , c () und d ) der Reihe nach ausgewertet werden. Dies ist wichtig, wenn sie Nebenwirkungen haben. Normalerweise dürfen Compiler Unterausdrücke in beliebiger Reihenfolge auswerten. Zum Beispiel in einem Funktionsaufruf:

someFunc(arg1, arg2, arg3)

Argumente können in beliebiger Reihenfolge ausgewertet werden. Beachten Sie, dass die Kommas im Funktionsaufruf keine Operatoren sind. Sie sind Trennzeichen.

efotinis
quelle
15
Hervorzuheben ist , dass es ,eine so niedrige Priorität hat, dass es sogar hinter sich selbst zurückbleibt ;) ... Das heißt: Komma als Operator hat eine niedrigere Priorität als Komma als Trennzeichen . Wenn Sie also den Komma als Operator in einem einzelnen Funktionsargument, einer Variablenzuweisung oder einer anderen durch Kommas getrennten Liste verwenden möchten, müssen Sie Klammern verwenden, z. B.:int a = 1, b = 2, weirdVariable = (++a, b), d = 4;
underscore_d
68

Der Kommaoperator:

  • hat die niedrigste Priorität
  • ist linksassoziativ

Eine Standardversion des Kommaoperators ist für alle Typen (integriert und benutzerdefiniert) definiert und funktioniert wie folgt exprA , exprB:

  • exprA wird ausgewertet
  • Das Ergebnis von exprAwird ignoriert
  • exprB wird ausgewertet
  • Das Ergebnis von exprBwird als Ergebnis des gesamten Ausdrucks zurückgegeben

Bei den meisten Operatoren kann der Compiler die Ausführungsreihenfolge auswählen, und es ist sogar erforderlich, die Ausführung zu überspringen, wenn dies das Endergebnis nicht beeinflusst (z. B. false && foo()wird der Aufruf von übersprungen foo). Dies ist jedoch beim Komma-Operator nicht der Fall und die obigen Schritte werden immer ausgeführt * .

In der Praxis funktioniert der Standard-Kommaoperator fast genauso wie ein Semikolon. Der Unterschied besteht darin, dass zwei durch ein Semikolon getrennte Ausdrücke zwei separate Anweisungen bilden, während die Kommatrennung alle als einen einzigen Ausdruck enthält. Aus diesem Grund wird in den folgenden Szenarien manchmal ein Kommaoperator verwendet:

  • Die C-Syntax erfordert einen einzelnen Ausdruck , keine Anweisung. zB inif( HERE )
  • Die C-Syntax erfordert eine einzelne Anweisung, nicht mehr, z. B. bei der Initialisierung der forSchleifefor ( HERE ; ; )
  • Wenn Sie geschweifte Klammern überspringen und eine einzige Aussage behalten möchten: if (foo) HERE ;(Bitte tun Sie das nicht, es ist wirklich hässlich!)

Wenn eine Anweisung kein Ausdruck ist, kann das Semikolon nicht durch ein Komma ersetzt werden. Zum Beispiel sind diese nicht erlaubt:

  • (foo, if (foo) bar)( ifist kein Ausdruck)
  • int x, int y (Variablendeklaration ist kein Ausdruck)

In Ihrem Fall haben wir:

  • a=b, c;Dies entspricht der a=b; c;Annahme, dass aes sich um einen Typ handelt, der den Kommaoperator nicht überlastet.
  • a = b, c = d;Dies entspricht der a=b; c=d;Annahme, dass aes sich um einen Typ handelt, der den Kommaoperator nicht überlastet.

Beachten Sie, dass nicht jedes Komma ein Kommaoperator ist. Einige Kommas, die eine ganz andere Bedeutung haben:

  • int a, b; --- Die Liste der Variablendeklarationen ist durch Kommas getrennt, dies sind jedoch keine Kommaoperatoren
  • int a=5, b=3; --- Dies ist auch eine durch Kommas getrennte Variablendeklarationsliste
  • foo(x,y)--- durch Kommas getrennte Argumentliste. In der Tat xund ykann in beliebiger Reihenfolge ausgewertet werden!
  • FOO(x,y) --- durch Kommas getrennte Makroargumentliste
  • foo<a,b> --- durch Kommas getrennte Vorlagenargumentliste
  • int foo(int a, int b) --- durch Kommas getrennte Parameterliste
  • Foo::Foo() : a(5), b(3) {} --- durch Kommas getrennte Initialisiererliste in einem Klassenkonstruktor

* Dies gilt nicht ganz, wenn Sie Optimierungen anwenden. Wenn der Compiler erkennt, dass ein bestimmter Code absolut keine Auswirkungen auf den Rest hat, werden die unnötigen Anweisungen entfernt.

Weiterführende Literatur: http://en.wikipedia.org/wiki/Comma_operator

CygnusX1
quelle
Ist es erwähnenswert, dass Sie bei operator ,Überlastung die Assoziativitätsgarantien verlieren (genau wie Sie die Kurzschlusseigenschaften der operator&&und operator||wenn sie überlastet sind) verlieren ?
YoungJohn
Der Komma-Operator ist linksassoziativ, unabhängig davon, ob er überladen ist oder nicht. Ein Ausdruck a, b, cbedeutet immer (a, b), cund nie a, (b, c). Die letztere Interpretation könnte sogar zu Kompilierungsfehlern führen, wenn Elemente unterschiedlichen Typs sind. Nach welcher Reihenfolge können Sie die Argumente bewerten? Da bin ich mir nicht sicher, aber vielleicht haben Sie Recht: Es kann vorkommen, dass cdas vorher ausgewertet wird, (a, b)auch wenn das Komma linksassoziativ ist.
CygnusX1
1
Nur ein kleiner Kommentar zur durch Kommas getrennten Initialisierungsliste in einem Klassenkonstruktor. Die Reihenfolge wird nicht durch die Position in der Liste bestimmt. Die Reihenfolge wird durch die Deklarationsposition der Klasse bestimmt. ZB struct Foo { Foo() : a(5), b(3) {} int b; int a; }evauliert b(3)vorher a(5). Dies ist wichtig, wenn Ihre Liste so ist : Foo() : a(5), b(a) {}. b wird nicht auf 5 gesetzt, sondern auf den nicht initialisierten Wert von a, vor dem Ihr Compiler möglicherweise warnt oder nicht.
Jonathan Gawrych
Ich bin kürzlich auf einen Kommaoperator mit zwei Gleitkommazahlen gestoßen. Was bringt es, eine Zahl auszuwerten und zu verwerfen?
Aaron Franke
Ich glaube nicht, dass jemand darauf antworten kann. Sie müssten es in einem Kontext zeigen. Wahrscheinlich eine separate Frage?
CygnusX1
38

Der Wert von awird sein b, aber der Wert des Ausdrucks wird sein c. Das ist in

d = (a = b, c);

a wäre gleich bund dwäre gleich c.

MobyDX
quelle
19
Fast richtig. Anweisungen haben keine Werte, Ausdrücke jedoch. Der Wert dieses Ausdrucks ist c.
Leon Timmermans
Warum wird dies anstelle von verwendet a = b; d = c;?
Aaron Franke
Dies ließ mich verstehen, über welche Nebenwirkungen die Leute sprachen.
Schnürsenkel
8

Der Wert von b wird a zugewiesen. Nichts wird passieren c

Prakash
quelle
2

Der Wert von a ist gleich b, da der Kommaoperator eine niedrigere Priorität hat als der Zuweisungsoperator.

Jason Carreiro
quelle
2

Ja Der Kommaoperator hat eine niedrige Priorität als der Zuweisungsoperator

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

Ausgabe: i = 3
Da der Kommaoperator immer den Wert ganz rechts zurückgibt.
Im Falle eines Kommaoperators mit Zuweisungsoperator:

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

Ausgabe: i = 1
Wie wir wissen, hat der Kommaoperator eine niedrigere Priorität als die Zuweisung .....

Roopam
quelle
Wie unterscheidet sich das zweite Beispiel davon, nur i = 1;in dieser Zeile zu stehen?
Aaron Franke
-3

Das Wichtigste zuerst: Komma ist eigentlich kein Operator, für den Compiler ist es nur ein Token, das im Kontext mit anderen Token eine Bedeutung erhält .

Was bedeutet das und warum sich die Mühe machen?

Beispiel 1:

Um den Unterschied zwischen der Bedeutung desselben Tokens in einem anderen Kontext zu verstehen, sehen wir uns dieses Beispiel an:

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

Normalerweise wird ein C ++ Anfänger würde denken , dass dieser Ausdruck könnte / würde die Dinge vergleichen , aber es ist absolut falsch, die Bedeutung der <, >und ,Token depent auf dem Nutzungskontext.

Die korrekte Interpretation des obigen Beispiels ist natürlich, dass es sich um eine Instanziierung einer Vorlage handelt.

Beispiel 2:

Wenn wir eine typische for-Schleife mit mehr als einer Initialisierungsvariablen und / oder mehr als einem Ausdruck schreiben, die nach jeder Iteration der Schleife ausgeführt werden soll, verwenden wir auch Komma:

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

Die Bedeutung des Kommas hängt vom Verwendungskontext ab, hier ist es der Kontext der forKonstruktion.

Was bedeutet eigentlich ein Komma im Kontext?

Um es noch komplizierter zu machen (wie immer in C ++), kann der Kommaoperator selbst überladen werden (dank Konrad Rudolph für den Hinweis).

Um auf die Frage zurückzukommen, den Kodex

a = b, c;

bedeutet für den Compiler so etwas wie

(a = b), c;

weil die Priorität des =Tokens / Operators höher ist als die Priorität des ,Tokens.

und dies wird im Kontext wie interpretiert

a = b;
c;

(Beachten Sie, dass die Interpretation vom Kontext abhängt, hier weder ein Funktions- / Methodenaufruf noch eine Template-Instanziierung.)

Quonux
quelle
1
sicher ist es, vielleicht habe ich die falsche Terminologie verwendet (für den Lexer ist es ein Token, sicher)
Quonux
2
Da man mit dem Operator (sic) arbeitet, ist das Komma tatsächlich ein Operator.
DragonLord
2
Während das Erkennen, ob ein gegebenes Komma-Token als Komma-Operator erkannt wird (im Gegensatz zum Beispiel zum Trennen von Argumenten), eine Herausforderung für sich sein kann, handelt es sich bei dieser Frage speziell um einen Komma-Operator .
CygnusX1