为什么以下程序的输出是什么?
#includeusing namespace std; int main(){ cout << "2+3 = " << cout << 2 + 3 << endl; }
产生
2+3 = 15
而不是预期的
2+3 = 5
这个问题已经多次关闭/重新开启.
在投票结束之前,请考虑有关此问题的元讨论.
无论是有意还是偶然,您都可以<<
在第一个输出线的末尾,您可能意味着;
.所以你基本上有
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); }
(在流结束时离开循环,或者当流字符不形成有效整数时).
现在,该转换运算符的确切定义已在标准的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:"我在xcode中找到了一个产生1的设置,另一个设置产生了一个地址:语言方言c ++ 98结合"标准库libc ++(支持c ++ 11的LLVM标准库)"产生1,而c ++ 98结合libstdc(gnu c ++标准库)产生一个地址;").你可以拥有一个C++ 03风格的编译器,它不能理解explicit
转换操作符(在C++ 11中是新的)与结合转换为的C++ 11风格的库相结合operator bool()
.通过这种混合,可以cout << cout
将其解释为cout << cout.operator bool()
简单的cout << true
并且打印"1"
.
正如伊戈尔所说,你得到的是一个C++ 11库,其中std::basic_ios
包含operator bool
而不是operator void*
,但不知何故未被声明(或被视为)explicit
.请参阅此处以获取正确的声明.
例如,符合标准的C++ 11编译器将给出相同的结果
#includeusing namespace std; int main() { cout << "2+3 = " << static_cast (cout) << 2 + 3 << endl; }
但在你的情况下,static_cast
(错误地)允许作为隐式转换.
编辑:由于这不是通常或预期的行为,因此了解您的平台,编译器版本等可能很有用.
编辑2:作为参考,代码通常写为
cout << "2+3 = " << 2 + 3 << endl;
或者作为
cout << "2+3 = "; cout << 2 + 3 << endl;
它将两种风格混合在一起,暴露了这个bug.
意外输出的原因是拼写错误.你可能意味着
cout << "2+3 = " << 2 + 3 << endl;
如果我们忽略具有预期输出的字符串,我们留下:
cout << cout;
从C++ 11开始,这是不正确的.std::cout
不能隐式转换为std::basic_ostream
(或非成员重载)接受的任何内容.因此,符合标准的编译器必须至少警告您这样做.我的编译器拒绝编译你的程序.
std::cout
可以转换为bool
,并且流输入运算符的bool重载将具有观察到的输出1.但是,该重载是显式的,因此它不应允许隐式转换.您的编译器/标准库实现似乎并不严格符合标准.
在预C++ 11标准中,这是很好的形式.那时候std::cout
有一个隐式转换运算符,void*
它有一个流输入运算符重载.然而,它的输出会有所不同.它会打印std::cout
对象的内存地址.
发布的代码不应该为任何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
更改:在现有布尔转换运算符中指定显式的使用
原理:澄清意图,避免使用变通方法.
对原始功能的影响:依赖于隐式布尔转换的有效C++ 2003代码将无法使用此国际标准进行编译.此类转换发生在以下条件中:
将值传递给带有bool类型参数的函数;
...
ostream operator <<是用bool参数定义的.因为转换为bool存在(并且不明确)是预C++ 11,cout << cout
被转换为cout << true
产生1.
根据C.2.15,从C++ 11开始,这不应该再编译.
您可以通过这种方式轻松调试代码.当您使用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);
缓冲: empty
首先,你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 = 15
最后你将值添加endl
到缓冲区的末尾,你有:
声明:
缓冲: 2+3 = 15\n
在此过程之后,缓冲区中的字符将从缓冲区逐个打印到标准输出.所以你的代码的结果是2+3 = 15
.如果你看一下这个你得到更多的1
从cout
你试图打印.通过<< cout
从语句中删除,您将获得所需的输出.