了解重载的 operator[] 示例

Understanding an overloaded operator[] example

我对我在 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;
}

我有点确定这会打印 24 但它却打印 44。我真的很想有人澄清这一点。是累积评价吗? 也是 << 二进制中缀 ?

提前致谢

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

此程序具有不确定的行为:编译器不需要按从左到右的顺序计算 i[0]i[2](C++ 语言赋予编译器这种自由,以便允许用于优化)。

例如,Clang does it in this instance, while GCC does not.

未指定求值顺序,因此您无法期望得到一致的输出,即使 运行 您的程序在特定机器上重复出现也是如此。

如果你想获得一致的输出,你可以将上面的措辞改写如下(或以某种等效的方式):

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

如您所见,本例中的 GCC no longer outputs 44

编辑:

如果出于某种原因,您真的希望表达式 cout << i[0] << i[2] 打印 24,则必须修改重载运算符(operator []operator <<) 显着,因为该语言有意使它无法判断哪个子表达式(i[0][i2])首先被评估。

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

但是,应该应用于 v 的增量作为参数传递给 operator [],您需要某种方式将其与原始 operator << 一起转发给 operator << =32=]对象。一种可能性是让 operator [] return 包含增量的数据结构以及对原始对象的引用:

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

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

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

这里是live example.

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

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

一种选择是更改您的代码,这样就没关系了。重写您的 [] 运算符,使其不会更改 Int,而 returns 改为新的 Int。无论如何,这可能是个好主意,因为这将使它与地球上 99% 的其他 [] 运算符保持一致。它还需要更少的代码:

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

你的另一个选择是保持你的 [] 不变,并将你的打印分成两个语句:

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

有些语言确实保证在 2*2+1*1 中首先完成 2*2。但不是 C++。

编辑:我没有我希望的那么清楚。让我们慢慢尝试。 C++ 有两种计算 2*2+1*1.

的方法

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

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

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

让我们用不同的表达式再试一次:i[0]+i[2].

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

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

我们得到了不同的答案,因为 [] 有副作用,所以我们先做 i[0] 还是 i[2] 很重要。根据 C++,这些都是有效的答案。现在,我们已准备好解决您原来的问题。正如您很快就会看到的,它几乎与 << 运算符无关。

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

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

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

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