为什么cout在此代码段中显示“ 2 + 3 = 15”?


126

为什么下面程序的输出是什么?

#include <iostream>
using namespace std;

int main(){

    cout << "2+3 = " <<
    cout << 2 + 3 << endl;
}

产生

2+3 = 15

而不是预期的

2+3 = 5

这个问题已经经历了多个关闭/重新打开周期。

在结束投票之前,请考虑一下有关此问题的元讨论


96
您要;在第一条输出行的末尾使用分号,而不是<<。您没有打印您认为要打印的内容。您正在做cout << cout,它会打印1cout.operator bool()我认为它使用)。然后,紧接着5(from 2+3),使其看起来像数字15。
Igor Tandetnik '17

5
@StephanLechner那可能正在使用gcc4。直到gcc5,他们才具有完全合规的流,尤其是直到那时,他们仍然具有隐式转换。
Baum mit Augen

4
@IgorTandetnik听起来像是答案的开始。对于这个问题,似乎有很多细微之处,这些细微之处在初读时并不明显。
Mark Ransom

14
人们为什么继续投票以结束这个问题?不是“请告诉我这段代码有什么问题”,而是“为什么这段代码会产生此输出?” 第一个答案是“您打错了”,是的,但是第二个答案需要解释编译器如何解释代码,为什么不是编译器错误以及如何获取“ 1”而不是指针地址。
jaggedSpire

6
@jaggedSpire如果这不是印刷错误,那么这是一个非常糟糕的问题,因为它故意使用了一个看起来像印刷错误的异常构造,而没有指出这是故意的。无论哪种方式,都应该受到密切的投票。(由于印刷错误或不良/恶意。这是一个寻求帮助的站点,而不是试图欺骗他人的站点。)
David Schwartz

Answers:


229

无论是有意还是无意,您都<<在第一个输出行的末尾,可能是您想要的;。所以你基本上有

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 << trueprint "1"


1
@TC我很确定C ++ 03和C ++ 98在此特定区域之间没有区别。我想我可以将所有C ++ 03的内容替换为“ pre-C ++ 11”,如果这有助于澄清问题。我对Linux等上的编译器和库版本控制的复杂性一点都不熟悉。我是Windows / MSVC的人。
Igor Tandetnik '17

4
我不是想在C ++ 03和C ++ 98之间挑剔。关键是libc ++仅是C ++ 11和更高版本;它不会尝试符合C ++ 98/03。
TC

45

正如Igor所说,您可以通过C ++ 11库获得此代码,该库std::basic_ios使用operator bool代替operator void*,但是不声明(或视为)explicit。请参阅此处以获取正确的声明。

例如,一个合格的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;

并且将两种样式混合在一起,从而暴露了该错误。


1
您建议的第一个解决方案代码中有错字。一个太多的运算符。
eerorika

3
现在我也正在这样做,它必须具有传染性。谢谢!
无用的

1
哈!:)在我的答案的初始编辑中,我建议添加分号,但在行尾没有意识到运算符。我认为,与OP一起,我们已经生成了可能具有的大多数错别字排列。
eerorika

21

出现意外输出的原因是拼写错误。你可能是说

cout << "2+3 = "
     << 2 + 3 << endl;

如果我们忽略具有预期输出的字符串,则将剩下:

cout << cout;

从C ++ 11开始,这是错误的形式。std::cout不能隐式转换为任何std::basic_ostream<char>::operator<<可接受的值(或非成员重载)。因此,符合标准的编译器至少必须警告您这样做。我的编译器拒绝编译您的程序。

std::cout可以转换为bool,并且流输入运算符的布尔重载将具有观察到的输出1。但是,该重载是显式的,因此它不应允许隐式转换。看来您的编译器/标准库的实现并不严格符合标准。

在C ++ 11之前的标准中,这格式很好。那时std::cout有一个隐式转换运算符,void*其中有一个流输入运算符重载。但是,输出结果会有所不同。它会打印std::cout对象的内存地址。


11

所发布的代码不应针对任何C ++ 11(或更高版本的兼容编译器)进行编译,但即使在C ++ 11之前的实现上也没有警告,也应进行编译。

区别在于C ++ 11明确地将流转换为布尔值:

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运算符<<是使用bool参数定义的。在C ++ 11之前,由于存在向布尔转换(但不是明确的)的cout << cout转换,因此转换为cout << true1。

并且根据C.2.15,此代码不应再从C ++ 11开始编译。


3
bool在C ++ 03中不存在转换,但是std::basic_ios::operator void*()作为条件或循环的控制表达式,这是有意义的。
Ben Voigt

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);

缓冲: 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

最后,将值of添加endl到缓冲区的末尾,您将拥有:

声明:

缓冲: 2+3 = 15\n

在此过程之后,缓冲区中的字符会从缓冲区中一张一张地打印到标准输出中。因此,代码的结果是2+3 = 15。如果您查看此内容1,则cout可以尝试打印其他内容。通过<< cout从语句中删除,您将获得所需的输出。


6
尽管这都是真的(格式精美),但我认为这是一个问题。我认为问题可以归结为“为什么首先cout << cout产生1?” ,而您刚刚断言它是在有关插入运算符链接的讨论中进行的。
没用的2007年

1
+1格式漂亮。考虑到这是您的第一个答案,很高兴您能提供帮助:)
gldraphael
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.