为什么cout在这段代码中打印“2 + 3 = 15”?
为什么下面的程序的输出是什么?
#include <iostream> using namespace std; int main(){ cout << "2+3 = " << cout << 2 + 3 << endl; }
产生
2+3 = 15
而不是预期的
2+3 = 5
这个问题已经进行了多次closures/重新打开循环。
在投票结束之前,请考虑关于这个问题的这个meta讨论 。
无论是有意还是无意,你都有<<
在第一条输出线的末尾,你可能意思的地方;
。 所以你基本上有
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"
?
事实certificate,这可能是令人惊讶的,微妙的。 std::cout
,通过它的基类std::basic_ios
,提供了一个特定的types转换运算符 ,用于在布尔上下文中使用,如
while (cout) { PrintSomething(cout); }
这是一个很不好的例子,因为输出很难失败 – 但是std::basic_ios
实际上是input和输出stream的基类,而对于input来说,它更有意义:
int value; while (cin >> value) { DoSomethingWith(value); }
(在stream结束时出现循环,或者stream字符不构成有效整数时)。
现在,这个转换运算符的确切定义已经在标准的C ++ 03和C ++ 11版本之间改变了。 在较老的版本中,它是operator void*() const;
(通常实现为return fail() ? NULL : this;
),而在newer中则是explicit operator bool() const;
(通常简单实现为return !fail();
)。 这两个声明在布尔上下文中都可以正常工作,但是当在这种上下文之外使用(mis)时,其行为会有所不同。
特别是在C ++ 03规则下, cout << cout
将被解释为cout << cout.operator void*()
并打印一些地址。 在C ++ 11规则下, cout << cout
根本不应该编译,因为运算符声明为explicit
,因此不能参与隐式转换。 这实际上是变化的主要动机 – 防止无意义的代码编译。 符合任一标准的编译器不会生成打印"1"
的程序。
显然,某些C ++实现允许混合和匹配编译器和库以产生不一致结果的方式(引用@StephanLechner:“我在xcode中find一个产生1的设置,另一个设置产生一个地址:Language dialect c ++ 98结合“标准库libc ++(支持c ++ 11的LLVM标准库)”产生1,而c ++ 98与libstdc(gnu c ++标准库)结合产生一个地址;“)。 你可以有一个C ++ 03风格的编译器,它不理解explicit
转换运算符(这是C ++ 11中的新增function),并与一个将operator bool()
定义为转换的C ++ 11样式库相结合。 有了这样的混合, cout << cout
可以被解释为cout << cout.operator bool()
,而cout << true
并打印"1"
。
正如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;
它将两种风格混合在一起,揭露了这个bug。
意外输出的原因是一个错字。 你可能的意思
cout << "2+3 = " << 2 + 3 << endl;
如果我们忽略具有预期输出的string,我们将留下:
cout << cout;
自C ++ 11以来,这是不合格的。 std::cout
不能隐式转换为std::basic_ostream<char>::operator<<
(或非成员重载)接受的任何东西。 因此符合标准的编译器至less要警告你这样做。 我的编译器拒绝编译你的程序。
std::cout
将被转换为bool
,并且streaminput操作符的bool重载将具有观察到的输出1.但是,该重载是显式的,所以它不应该允许隐式转换。 看来你的编译器/标准库的实现不严格地符合标准。
在一个预先的C ++ 11标准中,这是很好的形成的。 当时std::cout
有一个隐式转换运算符void*
,它有一个streaminput运算符重载。 然而,输出的结果会有所不同。 它会打印std::cout
对象的内存地址。
发布的代码不应该为任何C ++ 11(或更高版本的一致性编译器)编译,但它应该在C ++ 11以前的版本中没有警告。
不同之处在于C ++ 11将一个stream转换为一个bool显式:
C.2.15第27项:input输出库[diff.cpp03.input.output] 27.7.2.1.3,27.7.3.4,27.5.5.4
更改:指定在现有布尔转换运算符中使用显式
理由:明确意图,避免解决方法。
对原始function的影响:依赖于隐式布尔转换的有效C ++ 2003代码将无法使用此国际标准进行编译。 这种转换发生在下列条件下:
- 将值传递给一个函数,该函数接受一个types为bool的参数;
…
ostream运算符<<用bool参数定义。 作为一个转换到布尔存在(而不是明确的)是前C + + 11, cout << cout
被翻译为cout << true
得到1。
根据C.2.15,这不应该再从C ++ 11开始编译。
您可以通过这种方式轻松debugging您的代码。 当你使用cout
你的输出被缓冲,所以你可以像这样分析它:
设想一下, cout
第一次出现代表缓冲区,运算符<<
代表追加到缓冲区的末尾。 运算符<<
结果是输出stream,在你的情况下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 = ")
,它将给定的string直接放入缓冲区并成为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
你将得到所需的输出。