为什么在cout和cin中使用位移运算符(<<和>>)?

88

问题就在标题中;我确定有一个逻辑性的解释,但现在我却被难住了!


12
我认为这是因为它们类似于箭头,暗示某种物质的流动方向。 - Pointy
2
只是猜测,但我想这是因为你正在从文件中“移入”或“移出”数据。 - Mark Ransom
9
为了完整起见:在这个上下文中,它们被称为插入运算符:http://www.cplusplus.com/reference/iostream/ostream/operator%3C%3C/ - ChristopheD
7
@Pointy: read()write() 这样的函数怎么处理?我认为用户自定义运算符应该具有与内置运算符类似的语义,例如 + 可以用于添加复数或几何向量。但是 ostream::operator<< 与位移操作无关。一些早期的 C++ 设计决策现在被认为存在问题,例如自动生成复制构造函数如果存在析构函数,则不一定需要在 operator<< 的选择中体现出逻辑相关性。 - Philipp
1
@Crowstar:我可以反问一下吗?为什么要使用插入和提取运算符进行位移操作?个人更喜欢使用流进行位操作 ;) - Matthieu M.
显示剩余8条评论
13个回答

78

根据《C++设计与演化》第8.3.1节:

提供输出运算符而不是命名的输出函数的想法是由Doug McIlroy通过与UNIX shell中的I/O重定向运算符(>、>>、|等)进行类比而提出的。

[...]

考虑了几个操作符用于输入和输出操作: 赋值运算符被认为可用于输入和输出,但它绑定方式不正确。例如,cout=a=b将被解释为cout=(a=b),大多数人似乎更喜欢输入运算符与输出运算符不同。 尝试使用<>操作符,但是“小于”和“大于”的含义深深植根于人们的思想中,以至于新的I/O语句在实际使用中几乎无法阅读(但对于<<>>则不是这种情况)。此外,'<'在大多数键盘上刚好在','上方,因此人们会编写如下表达式:

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 Howland
@Zac 谢谢你的示例;我将其添加到答案中,使其更完整。 - Abe Voelker
1
这是我偏爱的解释。我只是讨厌C++倡导改变运算符的含义。我尽量避免使用任何改变那个含义的库或代码。例如,MyWebRequest = "http://www.google.com"; 实际上通过赋值下载网页。虽然我理解它的必要性,但我认为运算符重载被过度滥用了。我更喜欢他们使用<-或->之类的东西,以便不改变含义或将位移运算符更改为其他内容。 - Rahly
@rahly:但是符号->已经有了现有的含义,符号对<-也是如此。 - Ben Voigt
你说得没错... 但我的意思并不是要改变现有运算符的含义... 或许可以使用 :> 和 <: ... auto tmp = myvar << 12; 没有实际意义。 - Rahly

13

来自《C++编程语言》作者Stroustrup的话:

重载运算符<<以表示“放置”会给出更好的记法,使程序员可以在单个语句中输出一系列对象。

但是为什么是<<?无法发明新的词汇。赋值运算符既适用于输入也适用于输出,但大多数人似乎更喜欢使用不同的运算符进行输入和输出。此外,=绑定错误;即cout=a=b意味着cout=(a=b)而不是(cout=a)=b。我尝试了运算符<>,但是“小于”和“大于”的含义在人们的思维中已经根深蒂固,新的I/O语句在实际使用中几乎无法阅读。


运算符重载是为C++发明的,流也是如此,为什么不能在新语言中添加一个新的运算符呢?反正这样做也不会使C++与C不兼容。
现在的C++主要通过std库来添加功能,而不是通过新的语言结构,为什么当时不这样做呢?
- Petruza

8
由于它们具有相对合理的优先级并且看起来不错。在C++中,您无法创建新的运算符或更改它们的优先级或分组规则,您只能重载现有运算符并更改它们的实际操作。
选择使用“<<”和“>>”具有一些不幸的副作用,因为它会推动这样一种想法,即输出将按顺序完成。虽然这对于实际输出是正确的,但由于巧妙的链接技巧所带来的计算效果却是错误的,这种情况经常令人惊讶。
更具体地说,请参考下文:
std::cout << foo() << bar() << std::eol;

这并不意味着在调用bar之前会先调用foo

编辑

在C++17中,顺序问题已经被“修复”。现在,对于<<>>运算符,计算顺序被指定为从左至右。在C++中仍然存在一些未指定顺序的地方(甚至是不存在顺序,这意味着计算可能会交错进行),但现在一些常见情况的行为方式变得可预测且具有可移植性,请参见此答案


但是谁在乎呢?如果你在输出语句中使用具有副作用的函数,那么你创建的代码无论如何都是难以阅读和维护的。(当然,在许多其他情况下,这个论点也成立。而且有一个很好的理由来强制执行顺序——当你犯了一个错误并产生了副作用时,使错误可重现。) - James Kanze
1
@JamesKanze:我发现许多C++程序员确实认为在示例代码中foo()被调用之前保证会调用bar()...他们编写这样的代码s << header() << body() << footer();其中body()计算了在footer()中使用的一些总数。这种错误对于函数参数来说不太常见。 - 6502
即使订单有保证,我也不会雇用写出这样代码的程序员。这种隐藏的依赖关系是真正的维护噩梦。 - James Kanze
3
@JamesKanze:这个评论听起来有点滑稽(请记住,你所说的是一个拥有 setwsetfill 这样可怕函数的库)。 - 6502

7

如果你把cin看作是键盘,cout看作是显示器,那么你输入的内容会被存储到变量中。

cin>>var;

或者你的变量内容显示在屏幕上。
cout<<var;

7

>><<只是操作符,您可以为自己的类实现自己的>><<。我想“某人”选择它们的原因可能是:a)它们类似于shell文件操作;b)重用现有的运算符,因为没有必要创建新的运算符。


4
主要是因为它们的结合性。插入和提取操作符从左到右进行结合,因此
std::cout << "Hello" << ' ' << 4 << 2;

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


4

在这个上下文中,它们不是位运算符,而被称为插入和提取运算符。

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

这些仅用于视觉解释。如果您学习开发自己的流和运算符重载,那么您甚至可以使用+进行输入,-进行输出 :)


这些运算符仍然是来自C语言的位移运算符,只是进行了重载。它们并不是新的运算符。 - Petruza

3

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

操作符的含义取决于左侧出现的数据类型。在cin和cout(以及其他流类型)中,<<和>> 操作符将值移入和移出流中。如果左操作数是整数,则该操作是您已经了解的C中的位运算。

操作符的含义没有固定,尽管其优先级是固定的。


1
插入运算符>><<分别用于输入流和输出流,因为输入流表示数据进入程序的流动,而输出流表示数据从程序中流出。由于这些插入运算符看起来像方向运算符(显示数据流动的方向),所以>>被选用作输入流,<<被选用作输出流。
看一下代码的一部分...
int Num1;
cin >> Num1;

如果您仔细观察,>>表示将数据流向程序中的变量(在程序中声明),这意味着数据的流向由输入流(此处为cin)完成。
同样的规则也适用于cout
int Num2 = 5;
cout << Num2;

这里的<<表示数据从程序中流出(由于Num2是程序的一部分),这是输出流的工作。
我希望您能理解所有这些。

1
你好!欢迎来到StackOverflow。我刚刚提交了一个标记代码的编辑(正在审核中)。您可以使用“{}”按钮进行缩进,也可以通过缩进四个空格来进行缩进。您还可以使用反引号(`)标记行内代码。 - rrauenza
"插入"运算符是一个幻想的名称,它们是官方的位移运算符,并与流类重载没有语义关联。 - Petruza

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接