什么情况下operator<<指代插入运算符,什么情况下指代位左移运算符?

25

当使用 operator << 时,何时指的是插入运算符,何时指的是按位左移?

以下代码将输出 10,此时 operator << 指的是左移运算符。

cout << a.b() << a.a.b << endl;  

这将输出11operator <<是插入运算符的一个引用。

cout << a.b();
cout << a.a.b ;

我感到困惑,什么时候使用 cout 结合 operator << 才会表示左移运算符?

#include <iostream> 
using namespace std; 

class A { 
public:
    A() { a.a = a.b = 1; }

    struct { int a, b; } a;

    int b(); 
}; 

int A::b(){
    int x=a.a;
    a.a=a.b;
    a.b=x; 
    return x;
};

 int main(){
    A a; 
    a.a.a = 0; 
    a.b(); 

    cout << a.b() << a.a.b << endl;      // ?????
    return 0;
}

5
默认情况下,它是一个“按位左移”操作符,适用于类似于int的类型。这是一个内置的工具。如果重载了<<,则可以用于其他目的。 - iammilind
7
这是基本的运算符优先级和重载,不值得提问。当操作数类型(根据优先级/结合性)清楚地告诉语言使用哪个重载时,<< 运算符是插入运算符还是位左移运算符。 (c ++) - underscore_d
3
如果这个例子是一个最小可复现示例(MCVE),并且以人们更容易理解的方式书写,那么它就不会变成完全与“评估顺序”有关的东西,也不会成为问题。 - underscore_d
7
你是如何得出将10作为位移的结果的结论的? - molbdnilo
10
谁点了赞?提出了错误的问题,基于错误的症状和缺乏背景知识的阅读,举例也过于混乱和格式极差。 - underscore_d
显示剩余9条评论
6个回答

41

这将输出10,operator<<指左移。

cout << a.b() << a.a.b << endl;

这是因为操作数的求值顺序是未指定的。在clang中,它输出11,但在gcc中,它输出10。

你的代码:

cout << a.b() << a.a.b << endl;

可以替换为:

std::cout.operator<<(a.b()).operator<<(a.a.b);  

clang首先评估a.b(),然后是a.a.b,g++则相反。由于您的a.b()修改变量��因此会得到不同的结果。

当您将代码重写为:

cout << a.b();
cout << a.a.b ;

那么您就有两个完整的表达式语句,这里与操作数评估相关的未指定行为不存在。 因此,您始终会获得相同的结果。


3
同意。楼主只使用了严格的“1”和“0”,这不太幸运,因为它掩盖了底层问题的求值顺序。 - WhozCraig

16
在您的情况下,所有的 operator << 都是输出流插入运算符,因为它们的左参数是类型为 ostream& 的,并且按从左到右的顺序进行分组。
输出的差异是由函数参数评估的顺序引起的。
cout << a.b() << a.a.b

operator<<(operator<<(cout, a.b()), a.a.b)

因此,输出取决于先评估哪个a.a.ba.b()。这实际上未被当前标准(C++14)指定,因此您也可能得到11

据我所知,在C ++ 17中,11将是两种情况的唯一有效输出,因为它强制执行从左到右的函数参数评估。

更新:这似乎不是真的,因为委员会决定(根据N4606),使用在P0145R2底部提到的不确定序列化参数评估。参见[expr.call]/5。

更新2:由于我们在这里谈论重载算子,因此N4606中适用[over.match.oper]/2,其中说:

然而,操作数按内置运算符所预定义的顺序排序。

因此,C++17中将明确定义评估顺序。显然,P0145的作者们已经预测到了这种误解:

我们不认为这种非确定性会带来任何实质性的优化好处,但它确实会使关于函数调用的评估顺序的混淆和危险得以持续存在。


3
@NathanOliver 不,Anton 关于这点是错的。更改的是 a.a.b 将不会与 operator<<(cout, a.b()) 交替执行。因此,要么在执行 a.a.b 前完全评估 operator<<(cout, a.b()),要么反之。例如,评估顺序 cout,然后是 a.a.b,再是 a.b() 将不再符合标准。 - Oktalist
2
其中之一的变化是,即使重载了operator<<,它也成为了一个序列点。 - Cubbi
1
虽然他们不幸地选择了函数参数(和参数初始化)的不定序列,但他们也改变了重载运算符的评估规则,现在它们按照相应的内置运算符指定的操作数进行评估,而不是作为函数调用。移位运算符现在被指定为从左到右进行评估,因此表达式仍将具有明确定义的评估顺序。(顺便说一下,这些是成员运算符函数。)@underscore_d - bogdan
1
@bogdan 你是对的。P0145R2的作者们显然已经预测到了这种混乱和危险。 - Anton Savin
1
他们自己制造了一些混乱,通过以强调他们个人偏好的变化方式编写提案,并未呈现替代方案的动机原因。正如@Cubbi所指出的那样,由于内置和重载运算符的操作数求值顺序的更改,OP的表达式最终将被定义得很好,这是来自同一篇论文的。 - Oktalist
显示剩余6条评论

16

你面临的问题与 << 运算符无关。在每种情况下,都调用了插入运算符。

然而,在命令行中,你面临的问题涉及 评估顺序

cout << a.b() << a.a.b << endl;

函数a.b()具有副作用。它交换了a.a.aa.a.b的值。因此,显然,无论在计算a.a.b的值之前还是之后调用a.b()都会产生不同的结果。

在C++中,求值顺序是未指定的,请参阅cppreference.com以获取更详细的讨论。


9
这个调用:
cout << a.b() << a.a.b << endl;

首先要考虑:
cout << a.b()

这对应于插入运算符,并返回对cout的引用。因此,指令将变为:

(returned reference to cout) << a.a.b

再次调用插入运算符,以此类推...
如果您的指令是:
cout << (a.b() << a.a.b) << endl;

括号内的部分将首先被考虑。
a.b() << a.a.b

这一次,你在两个 int 之间使用了一个操作符:编译器只能将其解析为位运算符的调用。


9

<<这样的二元运算符有两个属性,它们定义了运算符的使用方式:(运算符)优先级和(左或右)结合性。在这种情况下,结合性是关键,例如,请参见http://en.cppreference.com/w/c/language/operator_precedence<<运算符具有从左到右的结合性,因此它们从左到右进行排序(就像用括号一样):

((cout << a.b()) << a.a.b) << endl;

或者按照顺序排列为cout << a.b(),然后是<< a.a.b,最后是<< endl
在这种排序之后,每次使用给定类型的<<调用运算符重载,决定调用哪个重载函数,从而确定是cout操作还是位移操作。

4

没有括号,<<两侧的操作数决定了意义:int << int == 左移stream << any == 插入。 这种操作符的“复用”可能会令人困惑。但是可以通过使用括号来消除歧义:stream << (int << int) == “int”


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