为什么对cout和cin使用按位移位(<<和>>)?


76

问题确实在标题中;我确定这是合乎逻辑的,但是现在我很困惑!


10
我认为这是因为它们类似于表明某种物质流动的箭头。
Pointy

1
只是猜测,但我想这是因为您要将数据“移入”或移出文件。
Mark Ransom

8
为了完整起见:在这种情况下,这些操作称为插入运算符:cplusplus.com/reference/iostream/ostream/operator%3C%3C
ChristopheD

6
@Pointy:像read()和函数如何write()?我认为用户定义的运算符应具有与内置运算符相似的语义,例如+可以用于添加复数或几何矢量。但是ostream::operator<<与位移无关。现在,一些早期的C ++设计决策被认为是有问题的,例如,如果存在析构函数,则自动生成副本构造函数,因此选择不必一定有逻辑operator<<
菲利普

1
@Crowstar:我可以扭转这个问题吗?为什么将插入和提取运算符用于按位移位?就个人而言,我使用流的方式要比按位操作更多;)
Matthieu M.

Answers:


67

根据《 C ++的设计和演进》第8.3.1节:

Doug McIlroy提出了提供输出运算符而不是命名输出函数的想法,类似于UNIX Shell中的I / O重定向运算符(>,>>,|等)。

[...]

考虑了几种用于输入和输出操作的运算符:赋值运算符是输入和输出的候选者,但绑定方式错误。那cout=a=b将被解释为cout=(a=b),并且大多数人似乎更喜欢输入运算符与输出运算符不同。运算符<>经过了尝试,但是“小于”和“大于”的含义已深深地扎根在人们的脑海中,以至于新的I / O语句出于所有实际目的都是不可读的(<<and似乎并非如此>>) 。除此之外,大多数键盘上的'<'都在','上方,人们在写这样的表达式:

cout < x , y, z;

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


18

也许是因为它看起来与Unix追加操作类似,因为您实际上是在追加到输入/输出流?

例如

输出量

echo "foo" >> bar

输入项

sendmail -f test@domain.com << myemail.txt

(从Zac Howland窃取输入示例)


1
@Federico:确实如此。您可以<<在UNIX命令行上使用进行插入: sendmail -f test@domain.com << myemail.txt
扎克·豪兰

@Zac感谢您的示例;我将其添加到答案中以使其更加完整
Abe Voelker

1
这是我的首选解释。我只是讨厌C ++提倡改变运算符的含义。我试图远离任何会改变含义的库或代码。IE MyWebRequest =“ google.com ”; 通过作业实际下载网页。尽管我了解它的必要性,但我认为运算符重载会被滥用太多。我更希望他们使用<-或->之类的东西,以免改变含义或将位移运算符更改为其他内容。
拉赫利

@rahly:但令牌->已经具有现有意为好,这样做的标记对<-
本·福格特

您是对的...但是我的意思不是要更改现有运算符的含义....也许:>和<:.... auto tmp = myvar << 12; 没有任何实质意义
Rahly

11

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

重载运算符<<以表示“放入”可提供更好的表示法,并允许程序员在单个语句中输出一系列对象。

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


7

>>并且<<只是运算符,您可以实现自己的类>><<为您的类实现。

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



6

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

选择<<>>具有一些不幸的副作用,因为它在某种程度上推动了将遵循顺序完成输出的想法。尽管由于巧妙的链接技巧而对于实际输出而言是正确的,但是对于所涉及的计算却是错误的,这通常令人惊讶。

更具体地写

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

并不暗示foo会在之前被调用bar

编辑

使用C ++ 17,序列问题已“修复”。现在,对于<<>>运算符,评估顺序指定为从左到右。在C ++中,仍有许多地方没有指定评估顺序(甚至不存在,意味着评估可以交错),但是现在一些常见情况以可预测和可移植的方式运行,请参见此答案


但谁在乎?如果在输出语句中使用具有副作用的函数,则无论如何都将创建不可读且不可维护的代码。(当然,否则,该论点在许多其他情况下也适用。有一个很好的论据强加命令-当您犯错并产生副作用时,使错误可重现。)
James Kanze

1
@JamesKanze:我只是发现许多C ++程序员确实认为示例中的代码foo()一定要在bar()...之前被调用,并且他们编写的代码就像s << header() << body() << footer();在中body()计算使用的总和footer()。对于函数参数,这种错误的发生频率较低。
6502

即使订单得到保证,我也不会雇用写这样的代码的程序员。诸如此类的隐藏依赖性是真正的维护噩梦。
James Kanze 2012年

3
@JamesKanze:这听起来评论漫画(请记住,你是在谈论一个库,就是恐惧setwsetfill它)。
6502年


4

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

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

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


3

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

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

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


1

Bjarne选择它们是为了获得实际优先级,关联性和助记符的价值。

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

但这还可以。


1

插入运算符>><<分别与输入流和输出流一起使用,因为输入流意味着数据流进入程序,输出流意味着数据流出程序。由于这些插入运算符看起来像“方向”运算符(显示数据流的方向),因此>>选择“输入”流和<<“输出”流。

看一下代码部分...

int Num1;
cin >> Num1;

如果您仔细观察,>>这里显示的是数据到变量的流(在程序中声明),这意味着数据到程序的流,这是Input流(这里cin)的工作。

同样地cout

int Num2 = 5;
cout << Num2;

这里<<显示了数据从程序中流出(作为程序的Num2一部分),这是Output流的工作。

我希望所有这些对您都有意义。


1
嗨!欢迎使用StackOverflow。我刚刚提交了标记您代码的修改(正在审核中)。您可以使用{}按钮缩进,也可以缩进四个空格。您也可以使用反引号(`)标记内联代码。
rrauenza

0
cout << "Output sentence"; // prints Output sentence on screen
cout << 120;               // prints number 120 on screen
cout << x;                 // prints the content of x on screen 

<<运算符将其后面的数据插入到其前面的流中。在上面的示例中,它将常量字符串Output句子,数字常量120和变量x插入到标准输出流cout中。

标准输入设备通常是键盘。通过对cin流应用重载的提取(>>)运算符,可以处理C ++中的标准输入。运算符之后必须是变量,该变量将存储将要从流中提取的数据。例如:

int age;
cin >> age;

0

我假设您知道C ++允许运算符重载。通常,仅在语义完全可转移的情况下才重载运算符(例如,重载向量类的加法以将两个向量加在一起)。我认为您的问题是关于为什么要使用位移位运算符,将它们重载到iostream并赋予它们与原始目的完全不同的含义。之所以可以这样做,是因为移位操作与iostream所做的工作相去甚远,所以没有人会误以为<<或>>正在对iostream进行移位。而且它们之所以方便使用的原因还在于它们的顺序是先评估左侧的操作数,然后评估右侧的操作数,然后进行操作。

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


4
-1:左,右侧的评价的顺序<<>>不能保证。当人们编写s << foo() << bar()并希望foo被调用之前,这确实有时是bug的来源bar。可以保证的结果foo将在的结果之前发送到流,bar但是不能保证计算顺序。只有,||&&二进制运算符给出了这样的保证...并且该保证只有在不重载它们的情况下才存在。
6502
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.