我对在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>
答案 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。
免责声明:正如其他人所提到的那样,请记住,v
和ostream &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种方式。但有时候这很重要。例如,如果f
和g
是两个函数,而我们执行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 ++。
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}已经完成了。