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
.
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--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:
someValue?
(++x,++y)
oder --x
(abhängig von bool
Ergebnis 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 someValue
true
ist?
(someValue?(++x,++y):--x)
führt aus und erhöht x
und y
, um 11
und 11
zu sein--y
, der dann y
zurück zu 10
dekrementiertUm 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
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.
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);
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.
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: