webentwicklung-frage-antwort-db.com.de

Warum wertet der ternäre Operator mit Komma im wahren Fall nur einen Ausdruck aus?

Ich lerne gerade C++ mit dem Buch C++ Primer und eine der Übungen im Buch ist:

Erklären Sie, was der folgende Ausdruck bewirkt: someValue ? ++x, ++y : --x, --y

Was wissen wir? Wir wissen, dass der ternäre Operator eine höhere Priorität hat als der Komma-Operator. Mit binären Operatoren war das recht einfach zu verstehen, aber mit dem ternären Operator habe ich ein bisschen zu kämpfen. Bei binären Operatoren bedeutet "mit höherer Priorität", dass Klammern um den Ausdruck mit höherer Priorität verwendet werden können und die Ausführung nicht geändert wird.

Für den ternären Operator würde ich tun:

(someValue ? ++x, ++y : --x, --y)

das Ergebnis ist derselbe Code, der mir nicht hilft zu verstehen, wie der Compiler den Code gruppiert.

Beim Testen mit einem C++ - Compiler weiß ich jedoch, dass der Ausdruck kompiliert wird, und ich weiß nicht, wofür ein : - Operator für sich selbst stehen könnte. Der Compiler scheint den ternären Operator also richtig zu interpretieren.

Dann habe ich das Programm auf zwei Arten ausgeführt:

#include <iostream>

int main()
{
    bool someValue = true;
    int x = 10, y = 10;

    someValue ? ++x, ++y : --x, --y;

    std::cout << x << " " << y << std::endl;
    return 0;
}

Ergebnisse in:

11 10

Während auf der anderen Seite mit someValue = false Gedruckt wird:

9 9

Warum sollte der C++ - Compiler Code generieren, der für den True-Branch des ternären Operators nur x inkrementiert, während er für den False-Branch des ternären Operators sowohl x als auch y dekrementiert? ]?

Ich bin sogar so weit gegangen, Klammern um den wahren Zweig zu setzen:

someValue ? (++x, ++y) : --x, --y;

es ergibt sich aber trotzdem 11 10.

117
Aufziehvogel

Wie @ Rakete in ihrer ausgezeichneten Antwort sagte, ist dies schwierig. Ich möchte noch etwas hinzufügen.

Der ternäre Operator muss folgende Form haben:

logischer-oder-Ausdruck? Ausdruck: Zuweisungsausdruck

Wir haben also die folgenden Zuordnungen:

  • someValue: logischer-oder-Ausdruck
  • ++x, ++y: expression
  • ??? ist Zuweisungsausdruck--x, --y oder nur --x?

Tatsächlich ist es nur --x, weil ein Zuweisungsausdruck nicht als zwei durch Komma getrennte Ausdrücke analysiert werden kann (gemäß den Grammatikregeln von C++), also --x, --y kann nicht als Zuweisungsausdruck behandelt werden.

Was dazu führt, dass der ternäre (bedingte) Ausdrucksteil so aussieht:

someValue?++x,++y:--x

Es kann aus Gründen der Lesbarkeit hilfreich sein, ++x,++y als zu berechnend zu betrachten als ob in Klammern (++x,++y); Alles, was zwischen ? und : enthalten ist, wird sequenziert nach der Bedingung. (Ich werde sie für den Rest des Beitrags in Klammern setzen).

und in dieser Reihenfolge ausgewertet:

  1. someValue?
  2. (++x,++y) oder --x (abhängig von boolErgebnis von 1.)

Dieser Ausdruck wird dann als linker Unterausdruck für einen Kommaoperator behandelt, wobei der rechte Unterausdruck --y ist, wie folgt:

(someValue?(++x,++y):--x), --y;

Was bedeutet, dass die linke Seite ein Ausdruck mit verworfenen Werten ist, was bedeutet, dass sie definitiv ausgewertet wird, aber dann bewerten wir die rechte Seite und geben diese zurück.

Was passiert also, wenn someValuetrue ist?

  1. (someValue?(++x,++y):--x) führt aus und erhöht x und y, um 11 und 11 zu sein
  2. Der linke Ausdruck wird verworfen (obwohl die Nebenwirkungen des Inkrements bestehen bleiben)
  3. Wir werten die rechte Seite des Komma-Operators aus: --y, der dann y zurück zu 10 dekrementiert

Um das Verhalten zu "korrigieren", können Sie --x, --y mit Klammern gruppieren, um es in einen primären Ausdruck umzuwandeln, der a ist gültiger Eintrag für einen Zuweisungsausdruck *:

someValue?++x,++y:(--x, --y);

* Es ist eine ziemlich lustige lange Kette, die einen Zuweisungsausdruck mit einem primären Ausdruck verbindet:

Zuweisungsausdruck --- (kann bestehen aus) -> Bedingungsausdruck -> logischer-oder-Ausdruck -> logisch-und-Ausdruck -> inklusive-oder-Ausdruck -> exklusiv-oder-Ausdruck -> und-Ausdruck -> Gleichheitsausdruck -> relationaler Ausdruck - > Verschiebungsausdruck -> additiver Ausdruck -> multiplikativer Ausdruck -> pm-expression -> cast-expression -> unary-expression -> postfix-expression -> primärer Ausdruck

121
AndyG

Wow, das ist schwierig.

Der Compiler sieht Ihren Ausdruck als:

(someValue ? (++x, ++y) : --x), --y;

Der ternäre Operator benötigt ein :, es kann in diesem Zusammenhang nicht für sich selbst stehen, aber danach gibt es keinen Grund, warum das Komma zu dem falschen Fall gehören sollte.

Jetzt ist es möglicherweise sinnvoller, warum Sie diese Ausgabe erhalten. Wenn someValue wahr ist, dann ++x, ++y und --y wird ausgeführt, was y nicht effektiv ändert, sondern x um eins erweitert.

Wenn someValue falsch ist, dann --x und --y werden ausgeführt und beide um eins dekrementiert.

87
Rakete1111

Warum sollte der C++ - Compiler Code generieren, der für die True-Verzweigung des ternären Operators nur x inkrementiert?

Sie haben falsch interpretiert, was passiert ist. Der True-Branch erhöht sowohl x als auch y. Jedoch wird y sofort danach bedingungslos dekrementiert.

Dies geschieht folgendermaßen: da der bedingte Operator hat in C++ eine höhere Priorität als der Komma-Operator , analysiert der Compiler den Ausdruck wie folgt:

   (someValue ? ++x, ++y : --x), (--y);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^  ^^^^^

Beachten Sie die "verwaisten" --y nach dem Komma. Dies führt dazu, dass y dekrementiert wird, das ursprünglich inkrementiert wurde.

Ich bin sogar so weit gegangen, Klammern um den wahren Zweig zu setzen:

someValue ? (++x, ++y) : --x, --y;

Sie waren auf dem richtigen Weg, aber Sie haben einen falschen Zweig in Klammern gesetzt: Sie können dies beheben, indem Sie den Zweig else in Klammern setzen:

someValue ? ++x, ++y : (--x, --y);

Demo (druckt 11 11)

42
dasblinkenlight

Ihr Problem ist, dass der ternäre Ausdruck keine höhere Priorität als Komma hat. Tatsächlich kann C++ nicht einfach durch Vorrang genau beschrieben werden - und es ist genau die Interaktion zwischen dem ternären Operator und dem Komma, in der es zerfällt.

a ? b++, c++ : d++

wird behandelt als:

a ? (b++, c++) : d++

(Komma verhält sich so, als hätte es Vorrang). Auf der anderen Seite,

a ? b++ : c++, d++

wird behandelt als:

(a ? b++ : c++), d++

und der ternäre Operator hat höhere Priorität.

5
Martin Bonner

Ein Punkt, der in Antworten übersehen wurde (obwohl er in Kommentaren angesprochen wurde), ist, dass der bedingte Operator im realen Code immer (beabsichtigt?) Als Abkürzung zum Zuweisen eines von zwei Werten zu einer Variablen verwendet wird.

Der größere Kontext wäre also:

whatIreallyWanted = someValue ? ++x, ++y : --x, --y;

Was auf den ersten Blick absurd ist, so sind die Verbrechen vielfältig:

  • Die Sprache erlaubt lächerliche Nebenwirkungen bei einem Einsatz.
  • Der Compiler hat Sie nicht gewarnt, dass Sie bizarre Dinge tun.
  • Das Buch scheint sich auf 'Trick'-Fragen zu konzentrieren. Man kann nur hoffen, dass die Antwort im Hintergrund lautete: "Was dieser Ausdruck bewirkt, hängt von seltsamen Randfällen in einem erfundenen Beispiel ab, um Nebenwirkungen hervorzurufen, die niemand erwartet. Tun Sie dies niemals."
2
Taryn