Dlaczego wynik poniższego programu jest taki, jaki jest?
#include <iostream>
using namespace std;
int main(){
cout << "2+3 = " <<
cout << 2 + 3 << endl;
}
produkuje
2+3 = 15
zamiast oczekiwanego
2+3 = 5
To pytanie przeszło już wiele cykli zamykania / ponownego otwierania.
Przed głosowaniem za zamknięciem prosimy o rozważenie tej meta dyskusji na ten temat.
;
na końcu pierwszego wiersza wyjścia, a nie<<
. Nie drukujesz tego, co myślisz, że drukujesz. Robiszcout << cout
, co drukuje1
(cout.operator bool()
chyba używa ). Następnie5
(od2+3
) następuje natychmiast, dzięki czemu wygląda jak liczba piętnaście.Odpowiedzi:
Czy celowo, czy przypadkowo, masz
<<
na końcu pierwszego wiersza wyjścia, gdzie prawdopodobnie miałeś na myśli;
. Więc zasadniczo maszA więc pytanie sprowadza się do tego: dlaczego
cout << cout;
druk"1"
?Okazuje się to, być może zaskakująco, subtelne.
std::cout
, poprzez swoją klasę bazowąstd::basic_ios
, zapewnia pewien operator konwersji typu, który jest przeznaczony do użycia w kontekście logicznym, jak wTo dość kiepski przykład, ponieważ trudno jest doprowadzić do niepowodzenia wyjścia - ale w
std::basic_ios
rzeczywistości jest to klasa bazowa zarówno dla strumieni wejściowych, jak i wyjściowych, a dla danych wejściowych ma znacznie więcej sensu:(wychodzi z pętli na końcu strumienia lub gdy znaki strumienia nie tworzą prawidłowej liczby całkowitej).
Teraz dokładna definicja tego operatora konwersji została zmieniona między wersjami standardu C ++ 03 i C ++ 11. W starszych wersjach był
operator void*() const;
(zwykle implementowany jakoreturn fail() ? NULL : this;
), podczas gdy w nowszych jestexplicit operator bool() const;
(zwykle implementowany po prostu jakoreturn !fail();
). Obie deklaracje działają dobrze w kontekście logicznym, ale zachowują się inaczej, gdy (niewłaściwie) są używane poza tym kontekstem.W szczególności, zgodnie z regułami C ++ 03,
cout << cout
zostanie zinterpretowany jakocout << cout.operator void*()
i wypisze jakiś adres. Zgodnie z regułami C ++ 11cout << cout
nie powinien w ogóle kompilować, ponieważ operator jest zadeklarowany,explicit
a zatem nie może uczestniczyć w niejawnych konwersjach. To była w istocie główna motywacja do zmiany - zapobieżenie kompilacji bezsensownego kodu. Kompilator zgodny z żadnym standardem nie utworzyłby programu, który drukuje"1"
.Najwyraźniej niektóre implementacje C ++ pozwalają na miksowanie i dopasowywanie kompilatora i biblioteki w sposób, który daje niezgodny wynik (cytując @StephanLechner: „Znalazłem ustawienie w xcode, które daje 1, i inne ustawienie, które daje adres: Dialekt języka c ++ 98 w połączeniu z "Biblioteka standardowa libc ++ (biblioteka standardowa LLVM z obsługą c ++ 11)" daje 1, podczas gdy c ++ 98 w połączeniu z libstdc (biblioteka standardowa gnu c ++) daje adres; "). Możesz mieć kompilator w stylu C ++ 03, który nie rozumie
explicit
operatorów konwersji (które są nowe w C ++ 11) w połączeniu z biblioteką w stylu C ++ 11, która definiuje konwersję jakooperator bool()
. Z taką mieszanką staje się możliwecout << cout
zinterpretowanie jakocout << cout.operator bool()
, co z kolei jest prostecout << true
i drukuje"1"
.źródło
Jak mówi Igor, to masz z biblioteki C ++ 11, gdzie
std::basic_ios
maoperator bool
zamiastoperator void*
, ale jakoś nie jest zadeklarowana (lub traktowane jako)explicit
. Patrz tutaj dla prawidłowej deklaracji.Na przykład zgodny kompilator C ++ 11 da ten sam wynik z
ale w twoim przypadku
static_cast<bool>
jest (niesłusznie) dozwolona jako niejawna konwersja.Edycja: ponieważ nie jest to zwykłe lub oczekiwane zachowanie, warto znać swoją platformę, wersję kompilatora itp.
Edycja 2: w celach informacyjnych kod byłby zwykle zapisywany jako
lub jako
i to mieszanie tych dwóch stylów razem ujawniło błąd.
źródło
Przyczyną nieoczekiwanego wyniku jest literówka. Prawdopodobnie miałeś na myśli
Jeśli zignorujemy łańcuchy, które mają oczekiwany wynik, zostanie nam:
Od C ++ 11 jest to źle sformułowane.
std::cout
nie jest niejawnie konwertowany na cokolwiek, costd::basic_ostream<char>::operator<<
(lub przeciążenie niebędące składnikiem) zaakceptowałoby. Dlatego kompilator zgodny ze standardami musi przynajmniej ostrzec Cię przed tym. Mój kompilator odmówił skompilowania twojego programu.std::cout
byłoby konwertowane nabool
, a przeciążenie bool operatora wejściowego strumienia miałoby obserwowane wyjście równe 1. Jednak to przeciążenie jest jawne, więc nie powinno zezwalać na niejawną konwersję. Wygląda na to, że implementacja Twojego kompilatora / biblioteki standardowej nie jest ściśle zgodna ze standardem.W standardzie poprzedzającym C ++ 11 jest to dobrze sformułowane. Wtedy
std::cout
miał niejawny operator konwersji,void*
który ma przeciążenie operatora wejściowego strumienia. Wynik dla tego byłby jednak inny. wypisuje adres pamięcistd::cout
obiektu.źródło
Opublikowany kod nie powinien kompilować się dla żadnego C ++ 11 (lub nowszego zgodnego kompilatora), ale powinien kompilować się nawet bez ostrzeżenia o implementacjach starszych niż C ++ 11.
Różnica polega na tym, że C ++ 11 dokonał konwersji strumienia na jawną wartość bool:
operator ostream << jest definiowany za pomocą parametru bool. Ponieważ konwersja do bool istniała (i nie była jawna) była wcześniejsza niż C ++ 11,
cout << cout
została przetłumaczona na,cout << true
co daje 1.Zgodnie z C.2.15 nie powinno to już kompilować się od C ++ 11.
źródło
bool
W C ++ 03 nie istniała żadna konwersja , ale jest to,std::basic_ios::operator void*()
co ma znaczenie jako kontrolujące wyrażenie warunku lub pętli.W ten sposób możesz łatwo debugować swój kod. Kiedy używasz
cout
dane wyjściowe, są buforowane, więc możesz je analizować w następujący sposób:Wyobraź sobie, że pierwsze wystąpienie
cout
reprezentuje bufor, a operator<<
reprezentuje dołączenie do końca bufora.<<
W twoim przypadku wynikiem operatora jest strumień wyjściowycout
. Zaczynasz od:cout << "2+3 = " << cout << 2 + 3 << endl;
Po zastosowaniu powyższych zasad otrzymasz zestaw działań takich jak:
buffer.append("2+3 = ").append(cout).append(2 + 3).append(endl);
Jak powiedziałem wcześniej, wynikiem
buffer.append()
jest bufor. Na początku twój bufor jest pusty i musisz przetworzyć następującą instrukcję:komunikat:
buffer.append("2+3 = ").append(cout).append(2 + 3).append(endl);
bufor: empty
Najpierw masz,
buffer.append("2+3 = ")
który umieszcza dany ciąg bezpośrednio w buforze i staje siębuffer
. Teraz twój stan wygląda tak:komunikat:
buffer.append(cout).append(2 + 3).append(endl);
bufor: 2+3 =
Następnie kontynuujesz analizę swojego oświadczenia i natrafisz na
cout
argument do dołączenia na końcu bufora.cout
Jest traktowany jak1
tak można dołączyć1
do końca swojego bufora. Teraz jesteś w tym stanie:komunikat:
buffer.append(2 + 3).append(endl);
bufor: 2+3 = 1
Następną rzeczą, którą masz w buforze, jest to,
2 + 3
że dodawanie ma wyższy priorytet niż operator wyjściowy, najpierw dodasz te dwie liczby, a następnie umieścisz wynik w buforze. Po tym otrzymasz:komunikat:
buffer.append(endl);
bufor: 2+3 = 15
Na koniec dodajesz wartość
endl
do końca bufora i masz:komunikat:
bufor: 2+3 = 15\n
Po tym procesie znaki z bufora są kolejno wypisywane z bufora na standardowe wyjście. Więc wynik twojego kodu to
2+3 = 15
. Jeśli spojrzysz na to, dostaniesz dodatkowe1
odcout
próby drukowania. Usuwając<< cout
z oświadczenia, uzyskasz pożądany wynik.źródło
cout << cout
produkuje1
?” , i właśnie zapewniłeś, że tak jest w środku dyskusji na temat tworzenia łańcuchów operatorów wstawiania.