我最近在comp.lang.c ++看到了一段代码,从函数中返回一个静态整数的引用。代码是这样的
int& f()
{
static int x;
x++;
return x;
}
int main()
{
f()+=1; //A
f()=f()+1; //B
std::cout<<f();
}
当我使用我很酷的Visual Studio调试器调试应用程序时,我只看到一个对语句A的调用,并猜测我感到震惊。我一直认为i+=1
等于i=i+1
所以
f()+=1
等于f()=f()+1
,我会看到两次f()
来电,但我只看到一次。这到底是什么?我疯了还是我的调试器疯了,或者这是过早优化的结果?
答案 0 :(得分:27)
这就是标准所说的关于+=
和朋友的信息:
5.17-7:E1 op = E2形式的表达式的行为等同于 E1 = E1操作E2除了E1是 只评估过一次。[...]
所以编译器就是这样。
答案 1 :(得分:10)
i+=1
功能与i=i+1
相同。它实际上是以不同的方式实现的(基本上,它旨在利用CPU级别优化)。
但实际上,左侧只评估一次。它产生一个非const l值,只需要读取值,添加一个并将其写回。
为自定义类型创建重载运算符时,这一点更为明显。 operator+=
修改this
实例。 operator+
返回一个新实例。通常建议(在C ++中)首先编写oop + =,然后根据它编写op +。
(注意这仅适用于C ++;在C#中,op+=
完全按照您的假设:只是op+
的简写,而您无法创建自己的op + =。它会自动创建为了你的Op +)
答案 2 :(得分:9)
你的想法是合乎逻辑的但不正确。
i += 1;
// This is logically equivalent to:
i = i + 1;
但在逻辑上等同且相同并不相同 代码应该看起来像这样:
int& x = f();
x += x;
// Now you can use logical equivalence.
int& x= f();
x = x + 1;
除非您明确地将两个函数调用放入代码中,否则编译器不会进行两个函数调用。如果你的函数有副作用(就像你那样)并且编译器开始添加额外的难以看到隐式调用,那么实际上很难理解代码的流程,从而使维护非常困难。
答案 3 :(得分:3)
f()
返回对静态整数的引用。然后+= 1
在此内存位置添加一个 - 不需要在语句A中调用它两次。
答案 4 :(得分:0)
在我看过的每种支持+ =运算符的语言中,编译器会对左侧的操作数进行一次计算,以产生某种类型的地址,然后将其用于读取旧值并编写新值一。 + =运算符不仅仅是语法糖;正如您所注意到的,它可以实现表达语义,这通过其他方式很难实现。
顺便说一句,vb.net和Pascal中的“With”语句都有类似的功能。如下声明:
' Assime Foo is an array of some type of structure, Bar is a function, and Boz is a variable. With Foo(Bar(Boz)) .Fnord = 9 .Quack = 10 End With将计算Foo(Bar(Boz))的地址,然后将该结构的两个字段设置为值9和10。它在C到
中是等价的
{ FOOTYPE *tmp = Foo(Bar(Boz)); tmp->Fnord = 9; tmp->Quack = 10; }
但是vb.net和Pascal不公开临时指针。虽然可以在VB.net中实现相同的效果而不使用“With”来保存Bar()的结果,但使用“With”可以避免临时变量。