Effekt der Verwendung eines Kommas anstelle eines Semikolons in C und C ++

70

Ich habe bei der Überarbeitung verschiedener Teile des C- und C ++ - Codes mehrfach festgestellt, dass ein Komma anstelle eines Semikolons verwendet wird, um Anweisungen zu trennen. Etwas wie das;

int a = 0, b = 0;
a = 5, b = 5;

Wo ich erwartet hätte

int a = 0, b = 0;
a = 5; b = 5;

Ich weiß, dass C und C ++ die Verwendung von Kommas zur Trennung von Anweisungen (insbesondere von Schleifenüberschriften) zulassen, aber was ist der Unterschied zwischen diesen beiden Codeteilen, wenn überhaupt? Ich vermute, dass das Komma als Ergebnis des Ausschneidens und Einfügens belassen wurde, aber ist es ein Fehler und wirkt es sich auf die Ausführung aus?

SmacL
quelle
12
Erwähnenswert ist, dass sich der "Kommaoperator" in der zweiten Zeile semantisch von dem Komma unterscheidet, das Initialisierungsausdrücke in begrenzt int a = 0, b = 0;.
mmx
2
Es gibt auch dieses Problem mit dem Komma-Operator, int* a, b;das nicht dasselbe ist wieint* a; int* b;
JPvdMerwe
3
Deshalb setze ich den Zeiger / Referenz-Modifikator immer neben die Variable, nicht den Typ. int *a, b;
mskfisher
Es ist nur schlecht foo. Benutze es nicht. Dies führt zu schwer lesbarem Code.
Martin York
1
@mskfisher: Deshalb deklariere ich nie mehr als eine Variable in einer einzelnen Anweisung. Dann kann ich den Modifikator mit dem Elementtyp setzen, der eher den Konzepten in neueren stark (er) typisierten Sprachen entspricht.
Sam Harwell

Antworten:

100

Es macht keinen Unterschied in dem Code, den Sie gepostet haben. Im Allgemeinen trennt das Komma Ausdrücke wie ein Semikolon. Wenn Sie jedoch das Ganze als Ausdruck nehmen, bedeutet der Kommaoperator, dass der Ausdruck bis zum letzten Argument ausgewertet wird.

Hier ist ein Beispiel:

b = (3, 5);

Wird 3, dann 5 auswerten und letzteres b zuweisen. Also b = 5. Beachten Sie, dass die Klammern hier wichtig sind:

b = 3, 5;

Wird ausgewertet b = 3, dann ist 5 und das Ergebnis des gesamten Ausdrucks ist trotzdem 5 b == 3.

Der Komma-Operator ist besonders hilfreich in for-Schleifen, wenn Ihr Iterator-Code nicht einfach i++ist, Sie jedoch mehrere Befehle ausführen müssen. In diesem Fall funktioniert ein Semikolon nicht gut mit der For-Loop-Syntax.

Frank
quelle
1
+1, gute Erklärung. Ich möchte nur eine häufige Gefahr hinzufügen, die nützlich sein kann, wenn Sie mit vielen dieser Probleme zu tun haben: Tippen a[j, k]statt a[j][k]normalerweise führt zu unbeabsichtigten Ergebnissen.
Laura
Technisch gesehen ist das Komma ein binärer Operator, daher hat es nur einen linken und einen rechten Ausdruck. Wenn jedoch mehrere Kommas auf derselben Ebene verwendet werden, ist das rekursive Ergebnis tatsächlich, dass es bis zum letzten Ausdruck ausgewertet wird.
x4u
1
Dies ist nützlich, um eine void-init-Funktion (vor main) auszuführen, indem manchmal ein bool zugewiesen wird: bool s_dummy = (init_my_stuff (), true);
Macke
@ Mike Seymour, das ist schön zu wissen - ich habe es auf dem College gesehen (vor 7 Jahren), als ich von Pascal zu C wechselte - es war ziemlich schrecklich zu debuggen.
Laura
@Mike Nur um hinzuzufügen - ein [j, k] lässt sich gut im Visual Studio 2008-Compiler kompilieren, und da ich erwartet hatte, dass der Kommaoperator bis zum letzten Argument ausgewertet wird, habe ich ihn gleich einem [k]. Aber in gcc habe ich einen Laufzeitfehler! - ideone.com/vDdl5
ZoomIn
26

Das Komma ist ein Operator, der einen Wert zurückgibt, der immer das 2. (rechte) Argument ist, während ein Semikolon nur Anweisungen beendet. Auf diese Weise kann der Kommaoperator in anderen Anweisungen verwendet oder mehrere Anweisungen so verkettet werden, dass sie als eine angezeigt werden.

Hier wird die Funktion f (x) aufgerufen und dann x > yfür die if-Anweisung ausgewertet.

if( y = f(x), x > y )

Ein Beispiel, wenn es nur verwendet wird, um die Notwendigkeit eines Blocks zu vermeiden

if( ... )
   x = 2, y = 3;

if( ... ) {
   x = 2;
   y = 3;
}
x4u
quelle
9

Der Kommaoperator wertet alle Operanden von links nach rechts aus und das Ergebnis ist der Wert des letzten Operanden.

Dies ist vor allem in for-Schleifen nützlich, wenn Sie im "Inkrement" -Teil mehrere Aktionen ausführen möchten, z. B. (Umkehren einer Zeichenfolge).

for (int lower = 0, upper = s.size() - 1; lower < upper; ++lower, --upper)
    std::swap(s[lower], s[upper]);

Ein weiteres Beispiel, bei dem es sich möglicherweise um eine Option handelt (alle Vorkommen in einer Zeichenfolge finden):

#include <string>
#include <iostream>

int main()
{
    std::string s("abracadabra");
    size_t search_position = 0;
    size_t position = 0;

    while (position = s.find('a', search_position), position != std::string::npos) {
        std::cout << position << '\n';
        search_position = position + 1;
    }
}

Insbesondere logisch und kann für diese Bedingung nicht verwendet werden, da sowohl Null als auch Nicht-Null bedeuten können, dass das Zeichen in der Zeichenfolge gefunden wurde. Mit Komma wird dagegen position = s.find()jedes Mal aufgerufen, wenn die Bedingung ausgewertet wird, aber das Ergebnis dieses Teils der Bedingung wird einfach ignoriert.

Natürlich gibt es auch andere Möglichkeiten, die Schleife zu schreiben:

while ((position = s.find('a', search_position)) != std::string::npos)

oder nur

while (true) {
    position = s.find('a', search_position);
    if (position == std::string::npos)
        break;
    ...
}
Onkel Bens
quelle
5

Wie Frank erwähnt hat , verursacht die Verwendung des Kommaoperators in Ihrem Beispiel keinen Fehler. Der Kommaoperator kann aus mehreren Gründen verwirrend sein:

  • es wird nicht zu oft gesehen, weil es nur in bestimmten besonderen Situationen notwendig ist
  • Es gibt verschiedene andere syntaktische Verwendungen des Kommas, die wie ein Kommaoperator aussehen können - aber nicht (die Kommas zum Trennen von Funktionsparametern / Argumenten, die Kommas zum Trennen von Variablendeklarationen oder Initialisierern).

Da dies verwirrend und häufig unnötig ist, sollte der Kommaoperator bis auf einige sehr spezifische Situationen vermieden werden:

  • Es kann nützlich sein, mehrere Operationen in einem oder mehreren Steuerausdrücken einer forAnweisung auszuführen
  • Es kann in Präprozessor-Makros verwendet werden, um mehr als einen Ausdruck in einer einzelnen Anweisung auszuwerten. Dies wird normalerweise durchgeführt, damit ein Makro mehr als eine Sache ausführen kann und dennoch ein einzelner Ausdruck ist, sodass das Makro an Stellen "passt", die nur einen Ausdruck zulassen.

Der Komma-Operator ist per Definition ein hackiger Operator - er dient dazu, zwei Dinge zu hacken, bei denen nur einer erlaubt ist. Es ist fast immer hässlich, aber manchmal ist das alles, was Sie haben. Und dies ist das einzige Mal, dass Sie es verwenden sollten. Wenn Sie eine andere Option haben, verwenden Sie nicht den Komma-Operator.

Ich kann mir nicht allzu viele andere Gründe vorstellen, den Operator zu verwenden, da Sie einen ähnlichen Effekt erzielen können, wenn Sie die Ausdrücke in den meisten anderen Situationen in separaten Anweisungen auswerten (obwohl ich sicher bin, dass jemand einen Kommentar abgeben wird eine andere Verwendung, die ich übersehen habe).

Michael Burr
quelle
4

Eine Verwendung wäre beim Code-Golfen:

if (x == 1) y = 2, z = 3;
if (x == 1) { y = 2; z = 3; }

Die erste Zeile ist kürzer, aber das sieht zu verwirrend aus, um es in der regulären Entwicklung zu verwenden.

Laufsteg
quelle
4
... und wer so Code schreibt, bekommt bald das, was er verdient.
SF.
Um die Wirkung des Kommas gegenüber dem Semikolon hervorzuheben, sollte Ihre zweite Zeile wahrscheinlich ein Semikolon zwischen y und z verwenden, wie in{y=2;z=3;}
mskfisher
@mskfisher: Danke für das Bemerken, ich habe das zweite Beispiel falsch geschrieben
Laufsteg