为什么以下程序的输出是什么?
#include <iostream>
using namespace std;
int main(){
cout << "2+3 = " <<
cout << 2 + 3 << endl;
}
产生
2+3 = 15
而不是预期的
2+3 = 5
这个问题已经多次关闭/重新开启。
在投票结束之前,请考虑this meta discussion关于此问题。
答案 0 :(得分:228)
无论是有意还是偶然,您在第一个输出行的末尾都有<<
,您可能需要;
。所以你基本上有
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
提供了用于布尔上下文的a certain type conversion operator,如
while (cout) { PrintSomething(cout); }
这是一个相当糟糕的例子,因为它很难让输出失败 - 但std::basic_ios
实际上是输入和输出流的基类,对于输入它更有意义:
int value;
while (cin >> value) { DoSomethingWith(value); }
(在流结束时离开循环,或者当流字符不形成有效整数时)。
现在,此转换运算符的确切定义已在标准的C ++ 03和C ++ 11版本之间发生了变化。在旧版本中,它是operator void*() const;
(通常实现为return fail() ? NULL : this;
),而在较新版本中explicit operator bool() const;
(通常简称为return !fail();
)。这两个声明在布尔上下文中都可以正常工作,但在(错误)在这样的上下文之外使用时表现不同。
特别是,根据C ++ 03规则,cout << cout
将被解释为cout << cout.operator void*()
并打印一些地址。在C ++ 11规则下,cout << cout
根本不应该编译,因为运算符被声明为explicit
,因此不能参与隐式转换。事实上,这是改变的主要动机 - 防止无意义的代码编译。符合任一标准的编译器不会生成打印"1"
的程序。
显然,某些C ++实现允许以一种产生不一致结果的方式混合和匹配编译器和库(引用@StephanLechner:&#34;我在xcode中找到了一个产生1的设置,另一个设置是产生一个地址:语言方言c ++ 98结合&#34;标准库libc ++(支持c ++ 11的LLVM标准库)&#34;产生1,而c ++ 98结合libstdc(gnu c ++标准库)产生一个地址;&#34;)。您可以拥有一个C ++ 03风格的编译器,它不会理解explicit
转换运算符(这是C ++ 11中的新增功能)与C ++ 11风格的库相结合,它将转换定义为operator bool()
。通过这样的混合,cout << cout
可以被解释为cout << cout.operator bool()
,而cout << true
只是"1"
并打印getCanonicalName()
。
答案 1 :(得分:45)
正如伊戈尔所说,你得到的是一个C ++ 11库,其中std::basic_ios
有operator bool
而不是operator void*
,但不知何故未被声明(或被视为)explicit
。请参阅here以获取正确的声明。
例如,符合标准的C ++ 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;
它将两种风格混合在一起,暴露了这个bug。
答案 2 :(得分:21)
意外输出的原因是拼写错误。你可能意味着
cout << "2+3 = "
<< 2 + 3 << endl;
如果我们忽略具有预期输出的字符串,我们将留下:
cout << cout;
从C ++ 11开始,这是不正确的。 std::cout
不能隐式转换为std::basic_ostream<char>::operator<<
(或非成员重载)可接受的任何内容。因此,符合标准的编译器必须至少警告您这样做。我的编译器拒绝编译你的程序。
std::cout
可转换为bool
,并且流输入运算符的bool重载将使观察到的输出为1.但是,该重载是显式的,因此它不应允许隐式转换。您的编译器/标准库实现似乎并不严格符合标准。
在前C ++ 11标准中,这是一个很好的形式。当时std::cout
有一个隐式转换运算符void*
,它有一个流输入运算符重载。然而,它的输出会有所不同。它会打印std::cout
对象的内存地址。
答案 3 :(得分:11)
发布的代码不应该为任何C ++ 11(或后来的符合编译器)编译,但是它应该在C ++ 11之前的实现中编译时甚至没有警告。
不同之处在于C ++ 11将流转换为bool显式:
C.2.15第27条:输入/输出库[diff.cpp03.input.output] 27.7.2.1.3,27.7.3.4,27.5.5.4
更改:在现有布尔转换运算符中指定使用explicit 理由:明确意图,避免变通方法 对原始特性的影响:依赖于隐式布尔转换的有效C ++ 2003代码将失败 编译本国际标准。此类转换发生在以下条件中:
- 将值传递给采用bool类型参数的函数;
...
ostream operator&lt;&lt;用bool参数定义。由于转换为bool存在(并且不明确)是预C ++ 11,cout << cout
被转换为cout << true
,产生1。
根据C.2.15,从C ++ 11开始,这不应该再编译。
答案 4 :(得分:7)
您可以通过这种方式轻松调试代码。当您使用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.append("2+3 = ")
将给定的字符串直接放入缓冲区并变为buffer
。现在你的州看起来像这样:
陈述:buffer.append(cout).append(2 + 3).append(endl);
缓冲区: 2 + 3 =
之后你继续分析你的语句,你会遇到cout
作为参数附加到缓冲区的末尾。 cout
被视为1
,因此您会将1
附加到缓冲区的末尾。现在你处于这种状态:
陈述:buffer.append(2 + 3).append(endl);
缓冲区: 2 + 3 = 1
你在缓冲区中接下来的东西是2 + 3
,因为加法的优先级高于输出操作符,你首先要添加这两个数字,然后将结果放入缓冲区。之后你得到:
陈述:buffer.append(endl);
缓冲区: 2 + 3 = 1 5
最后,您将endl
的值添加到缓冲区的末尾,并且:
语句:
缓冲区: 2 + 3 = 1 5 \ n
在此过程之后,缓冲区中的字符将从缓冲区逐个打印到标准输出。因此,代码的结果为2+3 = 15
。如果你看一下,你可以从1
获得额外的cout
,你试图打印。通过从语句中删除<< cout
,您将获得所需的输出。