Pourquoi la sortie du programme ci-dessous est-elle ce qu'elle est?
#include <iostream>
using namespace std;
int main(){
cout << "2+3 = " <<
cout << 2 + 3 << endl;
}
produit
2+3 = 15
au lieu de l'attendu
2+3 = 5
Cette question a déjà subi plusieurs cycles de fermeture / réouverture.
Avant de voter pour la clôture, veuillez considérer cette méta-discussion sur cette question.
;
à la fin de la première ligne de sortie, non<<
. Vous n'imprimez pas ce que vous pensez imprimer. Vous faitescout << cout
, ce qui imprime1
(il utilisecout.operator bool()
, je pense). Puis5
(de2+3
) suit immédiatement, faisant ressembler le nombre quinze.Réponses:
Que ce soit intentionnellement ou par accident, vous avez
<<
à la fin de la première ligne de sortie, où vous vouliez probablement dire;
. Donc, vous avez essentiellementLa question se résume donc à ceci: pourquoi
cout << cout;
imprimer"1"
?Cela s'avère, peut-être étonnamment, subtil.
std::cout
, via sa classe de basestd::basic_ios
, fournit un certain opérateur de conversion de type qui est destiné à être utilisé dans un contexte booléen, comme dansC'est un exemple assez médiocre, car il est difficile de faire échouer la sortie - mais
std::basic_ios
c'est en fait une classe de base pour les flux d'entrée et de sortie, et pour l'entrée, cela a beaucoup plus de sens:(sort de la boucle à la fin du flux, ou lorsque les caractères du flux ne forment pas un entier valide).
Désormais, la définition exacte de cet opérateur de conversion a changé entre les versions C ++ 03 et C ++ 11 de la norme. Dans les anciennes versions, il était
operator void*() const;
(généralement implémenté en tant quereturn fail() ? NULL : this;
), tandis que dans les versions plus récentes, il étaitexplicit operator bool() const;
(généralement implémenté simplement en tant quereturn !fail();
). Les deux déclarations fonctionnent correctement dans un contexte booléen, mais se comportent différemment lorsqu'elles sont (mal) utilisées en dehors de ce contexte.En particulier, sous les règles C ++ 03,
cout << cout
serait interprété commecout << cout.operator void*()
et afficherait une adresse. Sous les règles de C ++ 11,cout << cout
ne doit pas du tout compiler, car l'opérateur est déclaréexplicit
et ne peut donc pas participer aux conversions implicites. C'était en fait la principale motivation du changement - empêcher la compilation de code absurde. Un compilateur conforme à l'une ou l'autre des normes ne produirait pas de programme qui imprime"1"
.Apparemment, certaines implémentations C ++ permettent de mélanger et de faire correspondre le compilateur et la bibliothèque de manière à produire un résultat non conforme (citant @StephanLechner: "J'ai trouvé un paramètre dans xcode qui produit 1, et un autre paramètre qui donne une adresse: dialecte de langue c ++ 98 combiné avec "Bibliothèque standard libc ++ (bibliothèque standard LLVM avec support c ++ 11)" donne 1, alors que c ++ 98 combiné avec libstdc (bibliothèque standard gnu c ++) donne une adresse; "). Vous pouvez avoir un compilateur de style C ++ 03 qui ne comprend pas
explicit
les opérateurs de conversion (qui sont nouveaux dans C ++ 11) combiné avec une bibliothèque de style C ++ 11 qui définit la conversion commeoperator bool()
. Avec un tel mélange, il devient possiblecout << cout
d'être interprété commecout << cout.operator bool()
, qui à son tour est simplementcout << true
et imprime"1"
.la source
Comme le dit Igor, vous obtenez cela avec une bibliothèque C ++ 11, où
std::basic_ios
a laoperator bool
place deoperator void*
, mais n'est pas déclaré (ou traité comme)explicit
. Voir ici pour la déclaration correcte.Par exemple, un compilateur C ++ 11 conforme donnera le même résultat avec
mais dans votre cas, le
static_cast<bool>
est (à tort) autorisé en tant que conversion implicite.Edit: Comme ce n'est pas un comportement habituel ou attendu, il peut être utile de connaître votre plate-forme, la version du compilateur, etc.
Edit 2: Pour référence, le code serait généralement écrit soit comme
ou comme
et c'est le mélange des deux styles qui a exposé le bogue.
la source
La raison de la sortie inattendue est une faute de frappe. Vous vouliez probablement
Si nous ignorons les chaînes qui ont la sortie attendue, nous nous retrouvons avec:
Depuis C ++ 11, celui-ci est mal formé.
std::cout
n'est pas implicitement convertible en quelque chose quistd::basic_ostream<char>::operator<<
(ou une surcharge non membre) accepterait. Par conséquent, un compilateur conforme aux normes doit au moins vous en avertir. Mon compilateur a refusé de compiler votre programme.std::cout
serait convertible enbool
, et la surcharge booléenne de l'opérateur d'entrée de flux aurait la sortie observée de 1. Cependant, cette surcharge est explicite, donc elle ne devrait pas permettre une conversion implicite. Il semble que l'implémentation de votre compilateur / bibliothèque standard ne soit pas strictement conforme à la norme.Dans un standard pré-C ++ 11, c'est bien formé. À l'époque, il y
std::cout
avait un opérateur de conversion implicite versvoid*
lequel a une surcharge d'opérateur d'entrée de flux. La sortie pour cela serait cependant différente. il imprimerait l'adresse mémoire de l'std::cout
objet.la source
Le code publié ne doit pas être compilé pour un compilateur conforme C ++ 11 (ou ultérieur), mais il doit être compilé sans même un avertissement sur les implémentations antérieures à C ++ 11.
La différence est que C ++ 11 a rendu explicite la conversion d'un flux en un booléen:
L'opérateur ostream << est défini avec un paramètre booléen. Comme une conversion en bool existait (et n'était pas explicite) est pré-C ++ 11, a
cout << cout
été traduit encout << true
ce qui donne 1.Et selon C.2.15, cela ne devrait plus être compilé à partir de C ++ 11.
la source
bool
n'existait en C ++ 03, mais il y en astd::basic_ios::operator void*()
qui est significatif en tant qu'expression de contrôle d'une condition ou d'une boucle.Vous pouvez facilement déboguer votre code de cette façon. Lorsque vous utilisez
cout
votre sortie est mise en mémoire tampon afin que vous puissiez l'analyser comme ceci:Imaginez que la première occurrence de
cout
représente le tampon et que l'opérateur<<
représente l'ajout à la fin du tampon. Le résultat de l'opérateur<<
est le flux de sortie, dans votre cascout
. Vous partez de:cout << "2+3 = " << cout << 2 + 3 << endl;
Après avoir appliqué les règles énoncées ci-dessus, vous obtenez un ensemble d'actions comme celle-ci:
buffer.append("2+3 = ").append(cout).append(2 + 3).append(endl);
Comme je l'ai déjà dit, le résultat
buffer.append()
est tampon. Au début, votre tampon est vide et vous devez traiter l'instruction suivante:déclaration:
buffer.append("2+3 = ").append(cout).append(2 + 3).append(endl);
tampon: empty
Vous avez d'abord
buffer.append("2+3 = ")
qui met la chaîne donnée directement dans le tampon et devientbuffer
. Maintenant, votre état ressemble à ceci:déclaration:
buffer.append(cout).append(2 + 3).append(endl);
tampon: 2+3 =
Après cela, vous continuez à analyser votre déclaration et vous rencontrez un
cout
argument à ajouter à la fin du tampon. Lecout
est traité comme1
si vous ajouterez1
à la fin de votre tampon. Vous êtes maintenant dans cet état:déclaration:
buffer.append(2 + 3).append(endl);
tampon: 2+3 = 1
La prochaine chose que vous avez dans le tampon est
2 + 3
et puisque l'addition a une priorité plus élevée que l'opérateur de sortie, vous ajouterez d'abord ces deux nombres, puis vous placerez le résultat dans le tampon. Après cela, vous obtenez:déclaration:
buffer.append(endl);
tampon: 2+3 = 15
Enfin, vous ajoutez de la valeur
endl
à la fin du tampon et vous avez:déclaration:
tampon: 2+3 = 15\n
Après ce processus, les caractères du tampon sont imprimés un par un du tampon vers la sortie standard. Donc, le résultat de votre code est
2+3 = 15
. Si vous regardez cela, vous obtenez plus1
decout
vous avez essayé d'imprimer. En supprimant<< cout
de votre relevé, vous obtiendrez le résultat souhaité.la source
cout << cout
produit-il1
en premier lieu?" , et vous venez d'affirmer que c'est le cas au milieu d'une discussion sur le chaînage des opérateurs d'insertion.