【问题标题】:Why does cout print "2 + 3 = 15" in this snippet of code?为什么 cout 在这段代码中打印“2 + 3 = 15”?
【发布时间】:2017-01-19 15:38:17
【问题描述】:

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

#include <iostream>
using namespace std;

int main(){

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

生产

2+3 = 15

而不是预期的

2+3 = 5

这个问题已经经历了多次关闭/重新打开循环。

在投票结束之前,请考虑this meta discussion 关于此问题。

【问题讨论】:

  • 您希望在第一行输出的末尾使用分号 ;,而不是 &lt;&lt;。你没有打印你认为你正在打印的东西。你正在做cout &lt;&lt; cout,它打印1(我认为它使用cout.operator bool())。然后5(来自2+3)紧随其后,使其看起来像数字十五。
  • @StephanLechner 那可能是在使用 gcc4。在 gcc5 之前,它们没有完全兼容的流,特别是在此之前它们仍然具有隐式转换。
  • @IgorTandetnik 这听起来像是答案的开始。这个问题似乎有很多微妙之处,在第一次阅读时并不明显。
  • 为什么人们一直投票结束这个问题?不是“请告诉我这段代码有什么问题”,而是“为什么这段代码会产生这个输出?”第一个的答案是“你打错了”,是的,但是第二个需要解释编译器如何解释代码,为什么它不是编译器错误,以及它如何得到“1”而不是指针地址。
  • @jaggedSpire 如果不是印刷错误,那么这是一个非常糟糕的问题,因为它故意使用了一个看起来像印刷错误的不寻常结构,而没有指出这是故意的。无论哪种方式,都值得一票。 (由于印刷错误或不良/恶意。这是一个寻求帮助的人的网站,而不是试图欺骗他人的人。)

标签: c++


【解决方案1】:

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

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 &lt;&lt; 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 &lt;&lt; cout 将被解释为cout &lt;&lt; cout.operator void*() 并打印一些地址。在 C++11 规则下,cout &lt;&lt; cout 根本不应该编译,因为运算符被声明为explicit,因此不能参与隐式转换。这实际上是改变的主要动机——防止编译无意义的代码。符合任一标准的编译器不会生成打印 "1" 的程序。

显然,某些 C++ 实现允许以产生不合格结果的方式混合和匹配编译器和库(引用@StephanLechner:“我在 xcode 中找到了一个生成 1 的设置,以及另一个生成地址的设置:语言方言 c++98 与“标准库 libc++(支持 c++11 的 LLVM 标准库)”相结合产生 1,而 c++98 与 libstdc(gnu c++ 标准库)相结合产生一个地址;“)。您可以将不理解 explicit 转换运算符(C++11 中的新功能)的 C++03 样式编译器与将转换定义为 operator bool() 的 C++11 样式库结合使用。通过这样的混合,cout &lt;&lt; cout 可以被解释为cout &lt;&lt; cout.operator bool(),而后者又只是cout &lt;&lt; true 并打印出"1"

【讨论】:

  • @T.C.我很确定在这个特定领域 C++03 和 C++98 之间没有区别。我想我可以用“pre-C++11”替换所有提到的 C++03,如果这有助于澄清问题的话。我完全不熟悉 Linux 等上编译器和库版本控制的复杂性;我是 Windows/MSVC 人。
  • 我并不想在 C++03 和 C++98 之间挑剔;关键是 libc++ 只有 C++11 和更新版本;它不会尝试符合 C++98/03。
【解决方案2】:

正如 Igor 所说,您可以通过 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&lt;bool&gt; 被(错误地)允许作为隐式转换。


编辑: 由于这不是通常或预期的行为,因此了解您的平台、编译器版本等可能会很有用。


编辑2:作为参考,代码通常写成

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

或作为

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

这两种样式混合在一起暴露了这个错误。

【讨论】:

  • 第一个建议的解决方案代码中有错字。一个太多的运算符。
  • 现在我也在做,一定会传染。谢谢!
  • 哈! :) 在我的答案的初始编辑中,我建议添加分号,但没有意识到行尾的运算符。我认为与 OP 一起,我们已经产生了最重要的拼写错误排列。
【解决方案3】:

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

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

如果我们忽略具有预期输出的字符串,我们会得到:

cout << cout;

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

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

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

【讨论】:

    【解决方案4】:

    发布的代码不应该为任何 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 运算符 cout << cout 被转换为 cout &lt;&lt; true 从而产生 1。

    并且根据 C.2.15,这不应该再从 C++11 开始编译。

    【讨论】:

    • 在 C++03 中不存在到 bool 的转换,但是 std::basic_ios::operator void*() 作为条件或循环的控制表达式是有意义的。
    【解决方案5】:

    您可以通过这种方式轻松调试代码。当您使用cout 时,您的输出会被缓冲,因此您可以像这样分析它:

    想象第一次出现的cout 表示缓冲区,运算符&lt;&lt; 表示追加到缓冲区的末尾。运算符&lt;&lt; 的结果是输出流,在您的情况下为cout。你从:

    cout &lt;&lt; "2+3 = " &lt;&lt; cout &lt;&lt; 2 + 3 &lt;&lt; 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 = 15

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

    声明:

    缓冲区:2+3 = 15\n

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

    【讨论】:

    • 虽然这一切都是正确的(并且格式精美),但我认为这是在乞求问题。我相信这个问题可以归结为“为什么cout &lt;&lt; cout首先会产生1?”,你刚刚断言它是在关于插入运算符链接的讨论中发生的.
    • +1 以获得漂亮的格式。考虑到这是您的第一个答案,很高兴您能提供帮助:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-25
    • 1970-01-01
    • 2022-12-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多