为啥 cout 和 cin 使用按位移位(<< 和 >>)?

Posted

技术标签:

【中文标题】为啥 cout 和 cin 使用按位移位(<< 和 >>)?【英文标题】:Why are bitwise shifts (<< and >>) used for cout and cin?为什么 cout 和 cin 使用按位移位(<< 和 >>)? 【发布时间】:2011-06-18 18:17:53 【问题描述】:

问题确实在标题中;我确信有一些合乎逻辑的东西,但现在我很难过!

【问题讨论】:

我认为这是因为它们类似于表示某种物质流动的箭头。 只是猜测,但我想这是因为您正在将数据从文件“移入”或“移出”。 为了完整起见:在这种情况下,这些被称为插入运算符:cplusplus.com/reference/iostream/ostream/operator%3C%3C @Pointy:read()write() 这样的函数怎么样?我认为用户定义的运算符应该具有与内置运算符相似的语义,例如,+ 可用于添加复数或几何向量。但是ostream::operator&lt;&lt; 与位移无关。一些早期的 C++ 设计决策现在被认为是有问题的,例如,如果存在析构函数,则自动生成复制构造函数,因此对于 operator&lt;&lt; 的选择不一定要符合逻辑。 @Crowstar:我可以换个问题吗?为什么插入和提取运算符用于按位移位?就我个人而言,我使用流比按位操作更频繁;) 【参考方案1】:

根据The Design and Evolution of C++的§8.3.1:

Doug McIlroy 通过类比 UNIX shell 中的 I/O 重定向操作符(>、>>、| 等)提出了提供输出操作符而不是命名输出函数的想法

[...]

考虑了几个运算符用于输入和输出操作: 赋值运算符是输入和输出的候选,但它绑定错误的方式。那就是cout=a=b 将被解释为cout=(a=b),而且大多数人似乎更喜欢输入运算符与输出运算符不同。 运算符&lt;&gt; 被尝试过,但是“小于”和“大于”的含义是如此牢固地植入人们的脑海中,以至于新的 I/O 语句实际上是不可读的(这似乎不是&lt;&lt;&gt;&gt; 就是这种情况。除此之外,在大多数键盘上,'

cout < x , y, z;

为此给出好的错误消息并不容易。

【讨论】:

【参考方案2】:

可能是因为它看起来类似于 Unix 附加操作,因为您本质上是附加到输入/输出流?

例如

输出

echo "foo" &gt;&gt; bar

输入

sendmail -f test@domain.com &lt;&lt; myemail.txt

(从 Zac Howland 窃取输入示例)

【讨论】:

@Federico:确实如此。在 UNIX 命令行上使用 &lt;&lt; 进行插入:sendmail -f test@domain.com &lt;&lt; myemail.txt @Zac 谢谢你的例子;我将其添加到答案中以使其更完整 这是我的首选解释。我只是讨厌 C++ 提倡改变运算符的含义。我尽量远离任何改变其含义的库或代码。 IE。 MyWebRequest = "google.com";通过作业实际下载网页。尽管我理解它的必要性,但我认为运算符重载被滥用得太多了。我更希望他们使用 之类的东西,以免改变含义或将位移运算符更改为其他东西。 @rahly:但是标记-&gt;也已经有了一个现有的含义,标记对&lt;-也是如此 你是对的......但我的意思不是改变现有运算符的含义......也许是 :> 和 <:.... auto tmp="myvar">【参考方案3】:

来自“C++ 编程语言”。 Stroustrup(语言作者)的话:

重载运算符&lt;&lt; 来表示“放入”提供了更好的表示法,并让程序员在单个语句中输出一系列对象。

但为什么是&lt;&lt;?不可能发明新的词法记号。赋值运算符是输入和输出的候选者,但大多数人似乎更喜欢对输入和输出使用不同的运算符。此外, = 绑定错误的方式;也就是说, cout=a=b 意味着 cout=(a=b) 而不是 (cout=a)=b 。我尝试了运算符&lt;&gt;,但是“小于”和“大于”的意思已经牢牢地植入了人们的脑海中,以至于新的 I/O 语句对于所有实际用途来说都是不可读的。

【讨论】:

【参考方案4】:

所以你记得如果你认为cin 是键盘,cout 是显示器,那么你输入的内容会进入变量

cin>>var;

或者你的变量的内容会出现在屏幕上

cout<<var;

【讨论】:

【参考方案5】:

&gt;&gt;&lt;&lt; 只是运算符,您可以为您的类实现自己的 &gt;&gt;&lt;&lt;

我想“有人”选择它们是因为:a)它们类似于 shell 文件操作 b)重用现有的运算符,因为不需要创建新的运算符

【讨论】:

【参考方案6】:

因为它们或多或少具有合理的优先级并且看起来不错。在 C++ 中,您不能创建新的运算符或更改它们的优先级或分组规则,您只能重载现有的运算符并更改它们实际执行的操作。

&lt;&lt;&gt;&gt; 的选择有一些不幸的副作用,因为它以某种方式推动了输出将按照顺序完成的想法。虽然这对于实际输出来说是正确的,这要归功于巧妙的链接技巧,但对于所涉及的计算来说却是错误的,这常常令人惊讶。

具体写法

std::cout << foo() << bar() << std::eol;

并不意味着foo 将在bar 之前被调用。

编辑

在 C++17 中,序列问题已“修复”。现在,&lt;&lt;&gt;&gt; 运算符的求值顺序指定为从左到右。在 C++ 中仍有一些地方未指定求值顺序(甚至不存在意味着可以交错求值),但一些常见情况现在以可预测和可移植的方式表现,请参阅this answer。

【讨论】:

但是谁在乎呢?如果您在输出语句中使用具有副作用的函数,那么您无论如何都会创建不可读和不可维护的代码。 (否则,当然,这个论点也适用于许多其他情况。并且有一个很好的论点来强加命令——当你犯了一个错误并确实产生了副作用时,使错误可以重现。) @JamesKanze:我只是发现许多 C++ 程序员确实认为在示例代码中foo() 保证在bar() 之前被调用...并且他们编写的代码类似于s &lt;&lt; header() &lt;&lt; body() &lt;&lt; footer();其中body() 计算footer() 中使用的一些总数。这种错误对于函数参数来说不太常见。 我不会雇佣这样的程序员,即使订单有保证。像这样的隐藏依赖是真正的维护噩梦。 @JamesKanze:那条评论听起来很滑稽(请记住,你说的是一个图书馆,里面有 setwsetfill 这样的恐怖)。【参考方案7】:

它们不是按位运算符,在这种情况下它们被称为插入和提取运算符。

http://www.cplusplus.com/doc/tutorial/basic_io/

这些仅用于视觉解释。如果你研究开发自己的流和运算符重载,那么你可以看到你甚至可以使用 + 输入和 - 输出:)

【讨论】:

【参考方案8】:

主要是因为它们的关联性。插入和提取运算符从左到右关联,所以

std::cout << "Hello" << ' ' << 4 << 2;

按照您的预期进行评估:首先使用"Hello",然后使用' ',最后使用42。当然,加法运算符operator+ 也从左到右关联。但是该运算符和其他具有从左到右关联性的运算符已经具有不同的含义。

【讨论】:

【参考方案9】:

这个答案不令人满意但正确:它们不是按位运算符。

运算符的含义由出现在其左侧的数据类型决定。对于 cin 和 cout(以及其他流类型),> 运算符将值移入和移出流。在左操作数是整数的情况下,该操作是您已经从 C 中知道的按位操作。

运算符的含义并不固定,尽管它的优先级是固定的。

【讨论】:

【参考方案10】:

Bjarne 选择它们是因为实际优先级、关联性和助记符价值。

优先级并不完美,例如布尔和位级运算符很麻烦。

不过还算可以。

【讨论】:

【参考方案11】:

插入运算符&gt;&gt;&lt;&lt; 分别与输入流和输出流一起使用,因为输入流 表示数据流入您的程序,输出流表示数据流出您的程序。 由于这些插入运算符看起来像定向运算符(显示数据流的方向), 所以&gt;&gt; 被选为输入流,&lt;&lt; 被选为输出流。

看看部分代码...

int Num1;
cin >> Num1;

如果你仔细观察,这里&gt;&gt; 显示数据流向变量(在程序中声明) 这意味着数据流向程序,这是输入流的工作(这里是cin)。

cout 也类似,

int Num2 = 5;
cout << Num2;

这里&lt;&lt; 显示了数据流出程序的流程(因为Num2 是程序的一部分),这是输出流的工作。

我希望这一切对你有意义。

【讨论】:

嗨!欢迎来到 ***。我刚刚提交了一个标记您的代码的编辑(正在审核中)。您可以使用 按钮或缩进四个空格来缩进它。您还可以使用反引号 (`) 标记内联代码。【参考方案12】:
cout << "Output sentence"; // prints Output sentence on screen
cout << 120;               // prints number 120 on screen
cout << x;                 // prints the content of x on screen 

标准输入设备通常是键盘。在 C++ 中处理标准输入是通过在 cin 流上应用提取 (>>) 的重载运算符来完成的。运算符后面必须跟将存储将从流中提取的数据的变量。例如:

int age;
cin >> age;

【讨论】:

【参考方案13】:

我假设您知道 C++ 允许运算符重载。通常,只有在语义完全可转移时才重载运算符(例如,重载向量类的加法以将两个向量相加)。我认为您的问题是指为什么要使用位移运算符,为 iostream 重载它们,并赋予它们与原始目的完全不同的含义。之所以可以这样做,是因为位移操作与 iostream 的作用相去甚远,以至于没有人会误以为 > 正在对 iostream 进行位移。它们使用方便的原因还在于它们的排序是先计算左边的操作数,然后计算右边的操作数,然后进行运算。这符合您在使用运算符从 iostream 追加或提取内容时想要发生的情况。

但是,对于最初的问题,为什么?我真的不知道,在我看来,> 很容易理解为从一个实体获取信息并将其放入另一个实体。为什么需要比这更复杂的原因?使用它们看起来很明智,因为它们的含义很明显.. 你对操作员有什么更好的要求?

【讨论】:

-1:不保证&lt;&lt;&gt;&gt;左右两边的求值顺序。当人们编写诸如s &lt;&lt; foo() &lt;&lt; bar() 之类的东西并期望在bar 之前调用foo 时,这确实有时是错误的来源。保证foo 的结果将在bar 的结果之前发送到流中,但不保证计算顺序。只有,||&amp;&amp; 二元运算符提供了这样的保证……而且这种保证只有在你不重载它们的情况下才会出现。

以上是关于为啥 cout 和 cin 使用按位移位(<< 和 >>)?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我用vs2010编c++程序时,用“cout”“cin”时,调试时说“cout”“cin”是未声明的标识符?

为啥我们需要绑定 std::cin 和 std::cout?

如何在 VB.NET 中按位移位?

scanf()和cin的区别,用scanf,则没有输出数据,为啥?

在cin / cout和scanf / printf期间在C ++中保存float和double precision

哪一位VC的高手能告诉我为啥WinMain函数中无法使用Cout或Cin?不胜感激!