Почему cout печатает "2 + 3 = 15" в этом фрагменте кода?
Почему вывод программы ниже, что это такое?
#include <iostream>
using namespace std;
int main(){
cout << "2+3 = " <<
cout << 2 + 3 << endl;
}
производит
2+3 = 15
вместо ожидаемого
2+3 = 5
Этот вопрос уже прошел несколько циклов закрытия/повторного открытия.
Перед тем, как начать голосование, рассмотрите эту мета-дискуссию об этой проблеме.
Ответы
Ответ 1
Умышленно или случайно вы имеете <<
в конце первой выходной строки, где вы, вероятно, имели в виду ;
. Таким образом, у вас есть
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
Итак, вопрос сводится к следующему: почему cout << cout;
печатать "1"
?
Это оказывается, возможно, удивительно, тонким. std::cout
через свой базовый класс std::basic_ios
предоставляет оператор преобразования определенного типа, который предназначен для использования в булевом контексте, как в
while (cout) { PrintSomething(cout); }
Это довольно плохой пример, так как трудно получить вывод для отказа, но std::basic_ios
на самом деле является базовым классом как для входных, так и для выходных потоков, а для ввода он имеет гораздо больше смысла:
int value;
while (cin >> value) { DoSomethingWith(value); }
(выходит из цикла в конце потока или когда символы потока не образуют действительное целое число).
Теперь точное определение этого оператора преобразования изменилось между версиями стандарта С++ 03 и С++ 11. В более старых версиях он был operator void*() const;
(обычно реализован как return fail() ? NULL : this;
), а в новее он explicit operator bool() const;
(обычно реализованный просто как return !fail();
). Оба объявления отлично работают в булевом контексте, но ведут себя по-другому, когда (неверно) используется вне такого контекста.
В частности, в соответствии с правилами С++ 03 cout << cout
будет интерпретироваться как cout << cout.operator void*()
и напечатать некоторый адрес. В соответствии с правилами С++ 11 cout << cout
не следует компилировать вообще, поскольку оператор объявлен explicit
и, следовательно, не может участвовать в неявных преобразованиях. На самом деле это была основная мотивация изменений - предотвращение компрометации бессмысленного кода. Компилятор, который соответствует стандарту, не будет создавать программу, которая печатает "1"
.
По-видимому, некоторые реализации С++ позволяют смешивать и сопоставлять компилятор и библиотеку таким образом, что создает несоответствующий результат (цитируя @StephanLechner: "Я нашел параметр в xcode, который производит 1, а другой параметр, который дает адрес: Языковой диалект С++ 98 в сочетании с" стандартной библиотекой libС++ (стандартная библиотека LLVM с поддержкой С++ 11) "дает 1, тогда как С++ 98 в сочетании с libstdc (стандартная библиотека gnu С++) дает адрес;" ). У вас может быть компилятор С++ 03, который не понимает explicit
операторов преобразования (которые являются новыми в С++ 11) в сочетании с библиотекой в стиле С++ 11, которая определяет преобразование как operator bool()
. При таком смешивании становится возможным, чтобы cout << cout
интерпретировался как cout << cout.operator bool()
, который, в свою очередь, просто cout << true
и печатает "1"
.
Ответ 2
Как говорит Игорь, вы получаете это с помощью библиотеки С++ 11, где std::basic_ios
имеет operator bool
вместо operator void*
, но каким-то образом не объявляется (или не рассматривается как) explicit
. См. здесь для правильного объявления.
Например, соответствующий компилятор С++ 11 даст тот же результат с
#include <iostream>
using namespace std;
int main() {
cout << "2+3 = " <<
static_cast<bool>(cout) << 2 + 3 << endl;
}
но в вашем случае static_cast<bool>
допускается (ошибочно) как неявное преобразование.
Изменить:
Поскольку это не обычное или ожидаемое поведение, может быть полезно знать вашу платформу, версию компилятора и т.д.
Изменить 2: Для справки код обычно записывается либо как
cout << "2+3 = "
<< 2 + 3 << endl;
или
cout << "2+3 = ";
cout << 2 + 3 << endl;
и он смешивает два стиля вместе, что выявило ошибку.
Ответ 3
Причиной неожиданного вывода является опечатка. Вероятно, вы имели в виду
cout << "2+3 = "
<< 2 + 3 << endl;
Если мы проигнорируем строки, которые имеют ожидаемый результат, мы остаемся с:
cout << cout;
Так как С++ 11, это плохо сформировано. std::cout
не является неявным образом конвертируемым ко всему, что примет std::basic_ostream<char>::operator<<
(или не-членная перегрузка). Поэтому компилятор, соответствующий стандартам, должен хотя бы предупредить вас об этом. Мой компилятор отказался компилировать вашу программу.
std::cout
будет конвертируемым в bool
, а перегрузка bool оператора ввода потока будет иметь наблюдаемый вывод 1. Однако эта перегрузка является явной, поэтому она не должна допускать неявное преобразование. Похоже, что реализация вашего компилятора/стандартной библиотеки строго не соответствует стандарту.
В стандарте pre-С++ 11 это хорошо сформировано. Тогда std::cout
имел оператор неявного преобразования в void*
, который имеет перегрузку оператора ввода потока. Выход для этого, однако, будет отличаться. он будет печатать адрес памяти объекта std::cout
.
Ответ 4
Опубликованный код не должен компилироваться для любого С++ 11 (или более позднего совместимого компилятора), но он должен компилироваться без предупреждения даже в версиях pre С++ 11.
Различие заключается в том, что С++ 11 сделал преобразование потока в явное выражение bool:
C.2.15 Статья 27: Библиотека ввода/вывода [diff.cpp03.input.output] 27.7.2.1.3, 27.7.3.4, 27.5.5.4
Изменить: указать использование явного в существующих операторах логического преобразования
Обоснование: Уточнить намерения, избежать обходных путей.
Влияние на оригинальную функцию: действительный код С++ 2003, который полагается на неявные логические преобразования, не сможет с этим международным стандартом. Такие преобразования происходят в следующих условиях:
- передача значения функции, которая принимает аргумент типа bool;
...
Оператор ostream < определяется параметром bool. Поскольку преобразование в bool существовало (и не было явным), это pre-С++ 11, cout << cout
было переведено на cout << true
, что дает 1.
И согласно C.2.15, это не должно больше компилироваться, начиная с С++ 11.
Ответ 5
Вы можете легко отладить свой код таким образом. Когда вы используете cout
, ваш вывод буферизируется, поэтому вы можете анализировать его следующим образом:
Представьте, что первое появление cout
представляет буфер, а оператор <<
представляет собой добавление к концу буфера. Результатом оператора <<
является выходной поток, в вашем случае cout
. Вы начинаете с:
cout << "2+3 = " << cout << 2 + 3 << endl;
После применения вышеуказанных правил вы получите набор действий, подобных этому:
buffer.append("2+3 = ").append(cout).append(2 + 3).append(endl);
Как я уже говорил, результатом buffer.append()
является буфер. В начале ваш буфер пуст, и вы должны выполнить следующее выражение:
: buffer.append("2+3 = ").append(cout).append(2 + 3).append(endl);
buffer: empty
Сначала у вас есть buffer.append("2+3 = ")
, который помещает данную строку непосредственно в буфер и становится buffer
. Теперь ваше состояние выглядит следующим образом:
: buffer.append(cout).append(2 + 3).append(endl);
buffer: 2 + 3 =
После этого вы продолжаете анализировать свое утверждение, и вы сталкиваетесь с cout
как аргумент для добавления в конец буфера. cout
рассматривается как 1
, поэтому вы добавите 1
в конец вашего буфера. Теперь вы находитесь в этом состоянии:
: buffer.append(2 + 3).append(endl);
buffer: 2 + 3 = 1
Следующее, что у вас есть в буфере, - 2 + 3
, и поскольку добавление имеет более высокий приоритет, чем оператор вывода, вы сначала добавите эти два числа, а затем вы поместите результат в буфер. После этого вы получите:
: buffer.append(endl);
buffer: 2 + 3 = 1 5
Наконец, вы добавляете значение endl
в конец буфера, и у вас есть:
утверждение:
buffer: 2 + 3 = 1 5 \n
После этого процесса символы из буфера печатаются из буфера в стандартный вывод один за другим. Таким образом, результат вашего кода 2+3 = 15
. Если вы посмотрите на это, вы получите дополнительные 1
из cout
, которые вы пытались распечатать. Удалив << cout
из вашего заявления, вы получите желаемый результат.