了解重载的 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。但有时这很重要。例如,如果 f
和 g
是两个函数,而我们做 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" 已经完成。
我对我在 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。但有时这很重要。例如,如果 f
和 g
是两个函数,而我们做 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" 已经完成。