了解重载的operator []示例

时间:2015-05-22 23:03:26

标签: c++ operator-overloading operators overloading binary-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;
}

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

提前致谢

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

2 个答案:

答案 0 :(得分:16)

此程序具有不确定的行为:编译器不需要以从左到右的顺序评估i[0]i[2](C ++语言为编译器提供了这种自由,以便进行优化)。

例如,Clang does it in this instance,而GCC does not

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

如果你想获得一致的输出,你可以按照以下方式(或以某种等效的方式)改写上述内容:

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

正如您所见,GCC no longer outputs 44就是这种情况。

修改

如果出于某种原因,您确实希望表达式cout << i[0] << i[2]打印24,则必须修改重载运算符的定义(operator []和{{1}很明显,因为语言故意无法判断首先评估哪个子表达式(operator <<i[0])。

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

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

Int

现在您只需要重写operator []以便它接受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) } }; 对象,通过将存储的delta应用于operator <<数据成员来修改引用的Decorator对象,然后打印它的值:

Int

这是live example

免责声明:正如其他人所提到的那样,请记住,vostream &operator<< (ostream &o, Decorator const& d) { d.i.v += d.v; o << d.i.v; return o; } 通常是非变异操作(更确切地说,它们不会改变状态分别编入索引和流插入的对象,所以除非你只是试图解决一些C ++琐事,否则你很不愿意编写这样的代码。

答案 1 :(得分: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*1中首先完成2 * 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。请注意,问题发生在调用<<之前。没有办法超载<<来阻止这种情况,因为当<<运行时,&#34;损坏&#34}已经完成了。