什么时候运营商<<请参考插入运算符以及何时进行按位左移?

时间:2016-08-10 10:17:45

标签: c++ operator-overloading associativity

operator <<何时引用插入运算符,何时引用按位左移?

这将输出10operator <<表示左移。

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

这将输出11operator <<指的是插入运算符。

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

我感到困惑,operator <<(当与cout一起使用时)何时会引用左移算子?

#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;
}

6 个答案:

答案 0 :(得分:40)

  

这将输出10,并且运算符&lt;&lt;参考左移。

     

cout&lt;&lt; a.b()&lt;&lt; a.a.b&lt;&lt; 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 ;

然后你有两个完整的表达式语句,这里没有与操作数评估相关的未指定行为。所以你总能获得相同的结果。

答案 1 :(得分: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 ++中的AFAIK 17 11将是两种情况的唯一有效输出,因为它强制执行从左到右的函数参数评估。

更新:这似乎不正确,因为委员会决定(从N4606开始)采用P0145R2底部提到的不确定顺序参数评估。见[expr.call] / 5。

Update2:由于我们在这里谈论重载运算符,N4606中的[over.match.oper] / 2适用,其中说

  

但是,操作数按照内置运算符规定的顺序排序。

的确,评估的顺序将在C ++ 17中得到很好的定义。这种误解显然是由P0145的作者预测的:

  

我们并不认为这种不确定性会带来任何实质性的额外优化效益,但它会使函数调用中的评估顺序长期存在混淆和危害

答案 2 :(得分:14)

您遇到的问题与&lt;&lt;&lt;&lt;运营商。在每种情况下,都会调用插入运算符。

但是,您在命令行中遇到有关评估顺序的问题

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

函数a.b()有副作用。它交换了值a.a.a和a.a.b.因此,很明显,在评估值a.a.b之前或之后调用a.b()。

在C ++中,未指定评估顺序,有关更详细的讨论,请参阅cppreference.com

答案 3 :(得分:9)

这个电话:

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

首先会考虑:

cout << a.b()

对应于插入运算符并将refence返回给cout。因此,指令将变为:

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

再次调用插入操作符等等......

如果您的指示是:<​​/ p>

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

括号之间的部分将首先考虑:

a.b() << a.a.b

这次,你有一个2 int之间的运算符:编译器只能将其解析为对按位运算符的调用。

答案 4 :(得分: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 - 操作还是移位。

答案 5 :(得分:4)

没有括号,<<两侧的操作数确定含义:int << int == shiftstream << any == insertion。 这个&#39;重用&#39;运营商可能会混淆,独立。但您可以使用括号来解决歧义:stream << (int << int) == "int"