了解重载的operator []示例

Bug*_*tGG 13 c++ overloading operator-overloading binary-operators operators

我对在c ++测试中看到的问题感到困惑.代码在这里:

#include <iostream>
using namespace std;

class Int {
public:
    int v;
    Int(int a) { v = a; }
    Int &operator[](int x) {
        v+=x;
        return *this;
    }
};
ostream &operator<< (ostream &o, Int &a) {
    return o << a.v;
}

int main() {
    Int i = 2;
    cout << i[0] << i[2]; //why does it print 44 ?
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

我确信这会打印,24但它会打印出来44.我真的希望有人澄清一下.这是累积评估吗?也是<< 二进制中缀

提前致谢

编辑:如果没有明确定义的运算符重载,有人可以在这里更好地实现重载运算符,以便打印24

And*_*owl 16

这个程序有不确定的行为:编译器不需要评估i[0],并i[2]在左到右的顺序(C++语言给出了这样的自由编译器,以允许优化).

例如,Clang在这种情况下做,而GCC没有.

评估顺序未指定,因此即使在特定计算机上重复运行程序,也无法获得一致的输出.

如果您想获得一致的输出,可以按照以下方式(或以某种等效的方式)重新表达上述内容:

cout << i[0];
cout << i[2];
Run Code Online (Sandbox Code Playgroud)

如您所见,GCC 在这种情况下不再输出44.

编辑:

无论出于何种原因,如果你真的想要cout << i[0] << i[2]打印表达式24,你将不得不显着地修改重载运算符(operator []operator <<)的定义,因为语言故意无法判断哪个子表达式(i[0][i2])首先被评估.

你得到的唯一保证是评估的结果将在评估结果之前i[0]插入,所以你最好的选择是让我们执行数据成员的修改,而不是.couti[2]operator <<Int'svoperator []

但是,应该应用的delta v作为参数传递给operator [],并且您需要某种方式将其转发到operator <<原始Int对象.一种可能性是让我们operator []返回一个包含delta的数据结构以及对原始对象的引用:

class Int;

struct Decorator {
    Decorator(Int& i, int v) : i{i}, v{v} { }
    Int& i;
    int v;
};

class Int {
public:
    int v;
    Int(int a) { v = a; }
    Decorator operator[](int x) {
        return {*this, x}; // This is C++11, equivalent to Decorator(*this, x)
    }
};
Run Code Online (Sandbox Code Playgroud)

现在您只需要重写operator <<以便接受一个Decorator对象,Int通过将存储的delta应用于v数据成员来修改引用的对象,然后打印其值:

ostream &operator<< (ostream &o, Decorator const& d) {
    d.i.v += d.v;
    o << d.i.v;
    return o;
}
Run Code Online (Sandbox Code Playgroud)

这是一个实例.

免责声明:正如其他人所提到的那样,请记住,operator []并且operator <<通常是非变异操作(更确切地说,它们不会改变分别被索引和流插入的对象的状态),因此非常不鼓励您编写代码除非你只是试图解决一些C++琐事.

  • @vsoftco:在这种情况下,行为不是未定义的,只是不确定的.调用重载`operator []`只是常规函数调用,它们不能交错(见第1.9/15段) (4认同)
  • @vsoftco也许将表达式重写为`operator <<(operator <<(cout,i.operator [](0)),i.operator [](2));`将使它更容易理解.对"operator []`的两次调用可以按任何顺序发生,但它们不会交错,这使得对"v"的修改不确定地排序,而不是无序. (2认同)

Mar*_* VY 5

为了解释这里发生了什么,让我们简单一点:cout<<2*2+1*1;.首先发生什么,2*2或1*1?一个可能的答案是2*2应该首先发生,因为它是最左边的东西.但是C++标准说:谁在乎?!毕竟,结果是5种方式.但有时候这很重要.例如,如果fg是两个函数,并且我们这样做f()+g(),则无法保证首先调用哪个函数.如果f打印消息但g退出程序,则可能永远不会打印消息.在你的情况下,i[2]之前被调用过i[0],因为C++认为无所谓.你有两个选择:

一种选择是更改代码,这无关紧要.重写您的[]运算符,使其不会更改Int,并返回一个新的Int.无论如何,这可能是一个好主意,因为这将使其与[]地球上所有其他运营商的99%保持一致.它还需要更少的代码:

Int &operator[](int x) { return this->v + x;}.

您的另一个选择是保持[]相同,并将您的打印分成两个语句:

cout<<i[0]; cout<<i[2];

有些语言实际上确保在2*2+1*12*2中首先完成.但不是C++.

编辑:我不像我希望的那样清晰.让我们慢慢来吧.C++有两种评估方式2*2+1*1.

方法1 : 2*2+1*1 ---> 4+1*1 ---> 4+1 --->5.

方法2 : 2*2+1*1 ---> 2*2+1 ---> 4+1 --->5.

在这两种情况下,我们得到相同的答案.

让我们用另一种表达方式再试一次:i[0]+i[2].

方法1 : i[0]+i[2] ---> 2+i[2] ---> 2+4 ---> 6.

方法2 : i[0]+i[2] ---> i[0]+4 ---> 4+4 ---> 8.

我们得到了不同的答案,因为它[]有副作用,所以无论我们是做i[0]还是先做都很重要i[2].根据C++,这些都是有效的答案.现在,我们准备好攻击你原来的问题.您很快就会看到,它与<<操作员几乎没有任何关系.

C++如何处理cout << i[0] << i[2]?和以前一样,有两种选择.

方法1 : cout << i[0] << i[2] ---> cout << 2 << i[2] ---> cout << 2 << 4.

方法2 : cout << i[0] << i[2] ---> cout << i[0] << 4 ---> cout << 4 << 4.

第一种方法将按照您的预期打印24.但是根据C++,方法2同样好,它会像你看到的那样打印44.请注意,问题发生在<<调用之前.没有办法超载<<以防止这种情况,因为在<<运行的时候,"损坏"已经完成.