7

我最近在 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(),但我只看到一个。这是什么鬼?我是疯了还是我的调试器疯了还是这是过早优化的结果?

4

5 回答 5

27

这就是标准所说的+=和朋友们:

5.17-7: 形式为 E1 op= E2 的表达式的行为等价于 E1 = E1 op E2 除了 E1 只计算一次。[...]

所以编译器是正确的。

于 2010-08-09T14:13:15.713 回答
10

i+=1功能上与 相同i=i+1。它实际上以不同的方式实现(基本上,它旨在利用 CPU 级别的优化)。

但本质上,左侧只被评估一次。它产生一个非常量的左值,这就是读取该值、加一个并将其写回所需的全部内容。

当您为自定义类型创建重载运算符时,这一点更为明显。 operator+=修改this实例。 operator+返回一个新实例。一般建议(在C++中)先写oop+=,再根据它写op+。

(请注意,这仅适用于 C++;在 C# 中,op+=正如您所假设的那样:只是 的简写op+,您不能创建自己的 op+=。它会自动从 Op+ 中为您创建)

于 2010-08-09T14:06:03.157 回答
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;

除非您将两个函数调用显式放入代码中,否则编译器不会进行两个函数调用。如果你的函数有副作用(就像你做的那样)并且编译器开始添加额外的难以查看的隐式调用,那么实际上很难理解代码的流程,从而使维护变得非常困难。

于 2010-08-09T14:08:34.757 回答
3

f()返回对静态整数的引用。然后+= 1向这个内存位置添加一个——不需要在语句 A 中调用它两次。

于 2010-08-09T14:08:40.793 回答
0

在我见过的每一种支持 += 运算符的语言中,编译器都会对左侧的操作数进行一次评估,以产生某种类型的地址,然后将其用于读取旧值和写入新值。+= 运算符不仅仅是语法糖;正如您所注意到的,它可以实现通过其他方式实现的表达语义。

顺便说一句,vb.net 和 Pascal 中的“With”语句都有类似的功能。像这样的声明:

' Assime Foo 是某种结构的数组,Bar 是一个函数,Boz 是一个变量。
  与 Foo(酒吧(Boz))
    .Fnord = 9
    .嘎嘎= 10
  结束于
将计算 Foo(Bar(Boz)) 的地址,然后将该结构的两个字段设置为值 9 和 10。它在 C 中相当于

  {
    FOOTYPE *tmp = Foo(Bar(Boz));
    tmp->Fnord = 9;
    tmp->嘎嘎= 10;
  }

但 vb.net 和 Pascal 不公开临时指针。虽然在 VB.net 中不使用“With”来保存 Bar() 的结果也可以实现相同的效果,但使用“With”可以避免临时变量。

于 2010-08-09T15:29:38.727 回答