为什么cout打印" 2 + 3 = 15"在这段代码中?

时间:2017-01-19 15:38:18

标签: c++

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

#include <iostream>
using namespace std;

int main(){

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

产生

2+3 = 15

而不是预期的

2+3 = 5

这个问题已经多次关闭/重新开启。

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

5 个答案:

答案 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_iosoperator 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,您将获得所需的输出。