Perché l’output del programma seguente è quello che è?
#include using namespace std; int main(){ cout << "2+3 = " << cout << 2 + 3 << endl; }
produce
2+3 = 15
invece del previsto
2+3 = 5
Questa domanda è già passata più cicli di chiusura / riapertura.
Prima di votare per chiudere, considera questa meta discussione su questo problema.
Sia intenzionalmente che per caso, hai <<
alla fine della prima linea di output, dove probabilmente intendevi ;
. Quindi essenzialmente
cout << "2+3 = "; // this, of course, prints "2+3 = " cout << cout; // this prints "1" cout << 2 + 3; // this prints "5" cout << endl; // this finishes the line
Quindi la domanda si riduce a questo: perché cout << cout;
stampare "1"
?
Questo risulta essere, forse sorprendentemente, sottile. std::cout
, tramite la sua class base std::basic_ios
, fornisce un certo tipo di operatore di conversione che deve essere usato nel contesto booleano, come in
while (cout) { PrintSomething(cout); }
Questo è un esempio piuttosto scarso, in quanto è difficile far fallire l'output, ma std::basic_ios
è in realtà una class base per i flussi di input e di output, e per l'input ha molto più senso:
int value; while (cin >> value) { DoSomethingWith(value); }
(esce dal ciclo alla fine del stream o quando i caratteri del stream non formano un numero intero valido).
Ora, la definizione esatta di questo operatore di conversione è cambiata tra le versioni C ++ 03 e C ++ 11 dello standard. Nelle versioni precedenti, era operator void*() const;
(in genere implementato come return fail() ? NULL : this;
), mentre in newer è explicit operator bool() const;
(in genere implementato semplicemente come return !fail();
). Entrambe le dichiarazioni funzionano bene in un contesto booleano, ma si comportano diversamente quando (errate) vengono utilizzate al di fuori di tale contesto.
In particolare, in base alle regole C ++ 03, cout << cout
sarebbe interpretato come cout << cout.operator void*()
e stamperà un indirizzo. In C ++ 11 regole, cout << cout
non dovrebbe compilare affatto, in quanto l'operatore è dichiarato explicit
e quindi non può partecipare a conversioni implicite. Questa era in effetti la motivazione principale del cambiamento, impedendo la compilazione del codice senza senso. Un compilatore conforms agli standard non produrrebbe un programma che stampa "1"
.
Apparentemente, alcune implementazioni C ++ consentono di combinare il compilatore e la libreria in modo tale da produrre risultati non conformi (citando @StephanLechner: "Ho trovato un'impostazione in xcode che produce 1 e un'altra impostazione che produce un indirizzo: Lingua dialetto c ++ 98 combinato con "Libreria standard libc ++ (libreria standard LLVM con supporto c ++ 11)" produce 1, mentre c ++ 98 combinato con libstdc (libreria standard gnu c ++) produce un indirizzo; "). È ansible avere un compilatore in stile C ++ 03 che non comprenda operatori di conversione explicit
(che sono nuovi in C ++ 11) combinati con una libreria in stile C ++ 11 che definisce la conversione come operator bool()
. Con un tale mix, diventa ansible che cout << cout
venga interpretato come cout << cout.operator bool()
, che a sua volta è semplicemente cout << true
e stampa "1"
.
Come dice Igor, si ottiene questo con una libreria C ++ 11, dove std::basic_ios
ha l’ operator bool
invece operator void*
, ma in qualche modo non è dichiarato (o trattato come) explicit
. Vedi qui per la dichiarazione corretta.
Ad esempio, un compilatore C ++ 11 conforms darà lo stesso risultato con
#include using namespace std; int main() { cout << "2+3 = " << static_cast(cout) << 2 + 3 << endl; }
ma nel tuo caso, il static_cast
viene (a torto) permesso come una conversione implicita.
Modifica: poiché questo comportamento non è normale o previsto, potrebbe essere utile conoscere la tua piattaforma, la versione del compilatore, ecc.
Modifica 2: per riferimento, il codice di solito dovrebbe essere scritto come
cout << "2+3 = " << 2 + 3 << endl;
o come
cout << "2+3 = "; cout << 2 + 3 << endl;
e sta mescolando insieme i due stili che hanno esposto il bug.
Il motivo dell’output inatteso è un errore di battitura. Probabilmente intendevi
cout << "2+3 = " << 2 + 3 << endl;
Se ignoriamo le stringhe che hanno l'output atteso, restiamo con:
cout << cout;
Dal momento che C ++ 11, questo è mal formato. std::cout
non è implicitamente convertibile in qualcosa che std::basic_ostream
(o un overload non membro) accetterebbe. Pertanto un compilatore conforms agli standard deve almeno avvisarti di farlo. Il mio compilatore si è rifiutato di compilare il tuo programma.
std::cout
sarebbe convertibile in bool
, e il bool overload dell'operatore di input del stream avrebbe l'output osservato di 1. Tuttavia, il sovraccarico è esplicito, quindi non dovrebbe consentire una conversione implicita. Sembra che l'implementazione del tuo compilatore / libreria standard non sia strettamente conforms allo standard.
In uno standard pre-C ++ 11, questo è ben formato. Allora std::cout
aveva un operatore di conversione implicito per void*
che ha un sovraccarico dell'operatore di input del stream. L'output per quello sarebbe comunque diverso. stamperebbe l'indirizzo di memoria dell'object std::cout
.
Il codice postato non dovrebbe essere compilato per alcun compilatore conforms C ++ 11 (o successivo), ma dovrebbe compilare senza nemmeno un avvertimento sulle implementazioni pre C ++ 11.
La differenza è che C ++ 11 ha reso esplicita la conversione di uno stream in un bool:
C.2.15 Clausola 27: Libreria di input / output [diff.cpp03.input.output] 27.7.2.1.3, 27.7.3.4, 27.5.5.4
Modifica: specificare l’uso di esplicito negli operatori di conversione booleani esistenti
Motivazione: chiarire le intenzioni, evitare soluzioni alternative.
Effetto sulla caratteristica originale: il codice valido C ++ 2003 che si basa su conversioni booleane implicite non riuscirà a compilare con questo standard internazionale. Tali conversioni si verificano nelle seguenti condizioni:
- passare un valore ad una funzione che accetta un argomento di tipo bool;
…
operatore ostream << è definito con un parametro bool. Poiché una conversione in bool esisteva (e non era esplicita) è pre-C ++ 11, cout << cout
stato tradotto in cout << true
che restituisce 1.
E secondo C.2.15, questo non dovrebbe più essere compilato a partire da C ++ 11.
Puoi facilmente eseguire il debug del tuo codice in questo modo. Quando usi cout
tuo output viene memorizzato nel buffer in modo da poterlo analizzare in questo modo:
Immaginate che la prima occorrenza di cout
rappresenti il buffer e che l’operatore <<
accoda alla fine del buffer. Il risultato dell'operatore <<
è il stream di uscita, nel tuo caso cout
. Si parte da:
cout << "2+3 = " << cout << 2 + 3 << endl;
Dopo aver applicato le regole sopra indicate ottieni una serie di azioni come questa:
buffer.append("2+3 = ").append(cout).append(2 + 3).append(endl);
Come ho detto prima il risultato di buffer.append()
è il buffer. All'inizio il tuo buffer è vuoto e hai la seguente dichiarazione da elaborare:
statement: buffer.append("2+3 = ").append(cout).append(2 + 3).append(endl);
buffer: vuoto
Innanzitutto hai buffer.append("2+3 = ")
che mette la stringa data direttamente nel buffer e diventa buffer
. Ora il tuo stato assomiglia a questo:
istruzione: buffer.append(cout).append(2 + 3).append(endl);
buffer: 2 + 3 =
Dopo di che continui ad analizzare la tua dichiarazione e ti imbatti in cout
come argomento da aggiungere alla fine del buffer. Il cout
viene considerato come 1
quindi aggiungerai 1
alla fine del tuo buffer. Ora sei in questo stato:
istruzione: buffer.append(2 + 3).append(endl);
buffer: 2 + 3 = 1
La prossima cosa che hai nel buffer è 2 + 3
e poiché l'aggiunta ha una precedenza più alta dell'operatore di output, devi prima aggiungere questi due numeri e poi metterai il risultato nel buffer. Dopo ciò ottieni:
istruzione: buffer.append(endl);
buffer: 2 + 3 = 1 5
Infine aggiungi il valore di endl
alla fine del buffer e hai:
dichiarazione:
buffer: 2 + 3 = 1 5 \ n
Dopo questo processo, i caratteri dal buffer vengono stampati dal buffer allo standard output uno per uno. Quindi il risultato del tuo codice è 2+3 = 15
. Se guardi questo, ottieni 1
aggiuntivo da te che hai provato a stampare. Rimuovendo << cout
dalla tua dichiarazione otterrai l'output desiderato.