在C ++中递增-何时使用x ++或++ x?


91

我目前正在学习C ++,不久前我已经了解了增量。我知道您可以使用“ ++ x”在之前进行增量,然后使用“ x ++”在之后进行增量。

不过,我真的不知道什么时候使用这两种方法中的任何一种...我从来没有真正使用过“ ++ x”,并且到目前为止一切正常,因此,我什么时候应该使用它?

示例:在for循环中,何时最好使用“ ++ x”?

另外,有人可以确切解释不同的增量(或减量)如何工作吗?我真的很感激。

Answers:


114

这不是偏好问题,而是逻辑问题。

x++处理完当前语句后,递增变量x的值。

++x处理当前语句之前递增变量x的值。

因此,只需确定您编写的逻辑即可。

x += ++i将增加i并将i + 1加到x。 x += i++将i加到x,然后增加i。


27
并且请注意,在for循环中,对于基元,绝对没有区别。许多编码风格都建议不要使用可能会被误解的增量运算符;也就是说,x ++或++ x应该只存在于自己的行中,而不应以y = x ++的形式存在。就我个人而言,我不喜欢这样,但这并不常见
Mikeage

2
如果单独使用,生成的代码几乎肯定是相同的。
Nosredna

14
这看起来像是学究式的(主要是因为它是:)),但是在C ++中,它x++是一个rvalue,其值x之前为递增,x++是一个lvalue,其值x之后为递增。这两个表达式都不能保证将实际的增量值存储回x时,只能保证它出现在下一个序列点之前。“处理当前语句之后”并不是严格准确的,因为某些表达式具有序列点,而某些语句是复合语句。
CB Bailey

10
实际上,答案是误导的。实际上,修改变量x的时间点可能没有什么不同。区别在于x ++被定义为返回x先前值的右值,而++ x仍引用变量x。
sellibitze

5
@BeowulfOF:答案暗示着一个不存在的命令。标准中没有什么可以说增量发生的时间。编译器有权将“ x + = i ++”实现为:int j = i; i = i + 1; x + = j;”(即“ i”在“处理当前语句”之前增加)。这就是为什么“ i = i ++”具有未定义的行为,也是我认为答案需要“调整”的原因。“ x”的描述+ = ++ i“是正确的,因为没有顺序建议:”将i递增,并将i + 1加到x“
。–理查德·科登

53

斯科特·迈耶斯(Scott Meyers)告诉您更喜欢使用前缀,除非在逻辑上需要使用后缀的情况下除外。

“更有效的C ++”第6项 -对我来说已经足够了。

对于那些没有这本书的人,这里是相关的报价。从第32页:

从成为C程序员的那一天开始,您可能会记得,增量运算符的前缀形式有时被称为“增量和获取”,而后缀形式通常被称为“获取和增量”。这两个词很重要,因为它们几乎都是形式规范。

并在第34页上:

如果您是那种担心效率的人,那么当您第一次看到后缀增加功能时,您可能会大汗淋漓。该函数必须为其返回值创建一个临时对象,并且上面的实现还创建了一个必须构造和销毁的显式临时对象。前缀增量功能没有这样的临时对象。


4
如果编译器没有意识到增量之前的值是不必要的,则它可能会在多条指令中实现后缀增量-复制旧值,然后增量。前缀增量应始终仅是一条指令。
gnud

8
我昨天碰巧用gcc进行了测试:在一个for循环中,在执行i++or或之后,该值被丢弃++i,生成的代码是相同的。
乔治

在for循环外尝试一下。作业中的行为必须不同。
duffymo

我明确不同意Scott Meyers的第二点-因为通常90%或更多的“ x ++”或“ ++ x”案例与任何分配都无关,并且优化器足够聪明,可以识别出不需要临时变量,因此这通常是不相关的在这种情况下被创建。在这种情况下,这两种形式是完全可以互换的。这样做的含义是,旧代码库中充斥着“ x ++”的代码应该不予理会-与在任何地方提高性能相比,将它们更改为“ ++ x”的可能性更大,可能会引入细微的错误。可以说最好使用“ x ++”并使人们思考。
omatai

2
您可以放心使用Scott Meyers,但如果您的代码非常依赖于性能,以至于++x和之间的性能差异都x++非常重要,那么无论使用哪种版本,无论使用哪种版本,都可以完全,正确地优化任一版本的编译器,这一点更为重要。上下文。“由于我使用的是这把破烂的旧锤子,所以我只能以43.7度的角度打入钉子”对于以仅43.7度的角度打入钉子来建造房屋的说法不佳。使用更好的工具。
安德鲁·亨利

28

cppreference递增迭代器时:

如果您不打算使用旧值,则应该使用前递增运算符(++ iter)而不是后递增运算符(iter ++)。后增量通常按以下方式实现:

   Iter operator++(int)   {
     Iter tmp(*this); // store the old value in a temporary object
     ++*this;         // call pre-increment
     return tmp;      // return the old value   }

显然,它的效率不如预增量。

预增量不会生成临时对象。如果您的对象创建成本很高,那么这可能会带来很大的不同。


8

我只是想注意到,如果您使用前置/后置增量(语义(前置/后置)无关紧要),则生成的代码通常是相同的。

例:

pre.cpp:

#include <iostream>

int main()
{
  int i = 13;
  i++;
  for (; i < 42; i++)
    {
      std::cout << i << std::endl;
    }
}

post.cpp:

#include <iostream>

int main()
{

  int i = 13;
  ++i;
  for (; i < 42; ++i)
    {
      std::cout << i << std::endl;
    }
}

_

$> g++ -S pre.cpp
$> g++ -S post.cpp
$> diff pre.s post.s   
1c1
<   .file   "pre.cpp"
---
>   .file   "post.cpp"

5
对于像整数这样的基本类型,可以。您是否检查过结果却发现类似的东西有什么区别std::map::iterator?当然,这两个运算符是不同的,但是我很好奇,如果不使用结果,编译器是否会将后缀优化为前缀。考虑到后缀版本可能包含副作用,我认为这是不允许的。
SEH

此外,“ 编译器可能会意识到您不需要副作用并对其进行优化 ”不应成为编写草率代码的借口,该代码使用更复杂的后缀运算符,而无需任何理由,除了可能有这么多事实假定的教材没有明显的理由使用后缀,并大量批发。
underscore_d

6

记住,最重要的事情是imo,x ++需要在实际进行增量操作之前返回值-因此,它必须制作对象的临时副本(增量操作)。它的效率不如++ x,后者就地递增并返回。

不过,值得一提的是,大多数编译器将能够尽可能地优化这些不必要的东西,例如,这两个选项都将在此处导致相同的代码:

for (int i(0);i<10;++i)
for (int i(0);i<10;i++)

5

我同意@BeowulfOF,尽管为清楚起见,我始终主张拆分语句,以便逻辑绝对清晰,即:

i++;
x += i;

要么

x += i;
i++;

所以我的答案是,如果您编写清晰的代码,那么这几乎就没有关系(如果那么重要,那么您的代码可能不够清晰)。


2

只是想再次强调++ x 比x ++ 更快(特别是如果x是任意类型的对象),因此,除非出于逻辑原因需要,否则应使用++ x。


2
我只想强调,这种强调很可能会引起误解。如果您正在查看一个以孤立的“ x ++”结尾的循环,并想到“啊哈!-这就是运行如此缓慢的原因!” 然后将其更改为“ ++ x”,则完全没有区别。优化器足够聪明,可以识别出没人要使用其结果时无需创建任何临时变量。这意味着到处都是“ x ++”的旧代码库应该不予理会-与在任何地方提高性能相比,您更容易引入更改它们的错误。
omatai

1

您正确解释了差异。这仅取决于您是否希望x在每次循环之前或之后递增。这取决于您的程序逻辑,什么才是合适的。

处理STL-Iterators(也实现这些运算符)时的一个重要区别是,它++创建迭代器指向的对象的副本,然后递增,然后返回该副本。另一方面,++ it首先执行增量,然后返回对迭代器现在指向的对象的引用。当性能的每一点都很重要或实现自己的STL-iterator时,这才最重要。

编辑:修复了前缀和后缀表示法的混淆


仅当条件中发生前/后增量/减量时,才说循环的“之前/之后”才有意义。多数情况下,它将位于continuation子句中,在该子句中它无法更改任何逻辑,尽管类类型使用后缀的速度可能会变慢,并且人们不应无故使用它。
underscore_d

1

++的后缀形式,-运算符遵循use-then-change规则,

前缀格式(++ x,-x)遵循规则change-then-use

范例1:

当使用cout将多个值与<<层叠时,则计算(如果有的话)从右到左进行,但是打印从左到右进行,例如,(如果val最初为10)

 cout<< ++val<<" "<< val++<<" "<< val;

将导致

12    10    10 

范例2:

在Turbo C ++中,如果在表达式中发现++或(以任何形式)的多次出现,则首先计算所有前缀形式,然后评估表达式,最后计算后缀形式,例如,

int a=10,b;
b=a++ + ++a + ++a + a;
cout<<b<<a<<endl;

在Turbo C ++中的输出将是

48 13

而现代编译器的输出将是(因为它们严格遵守规则)

45 13
  • 注意:建议不要在一个表达式中对同一变量多次使用增量/减量运算符。此类
    表达式的处理/结果因编译器而异。

并不是包含多个inc / decrement操作的表达式“在编译器之间会有所不同”,而是更糟糕的是:序列点之间的这种多次修改具有未定义的行为并毒害了程序。
underscore_d

0

在考虑代码的清晰度时,了解语言语法很重要。考虑复制一个字符串,例如使用后增量:

char a[256] = "Hello world!";
char b[256];
int i = 0;
do {
  b[i] = a[i];
} while (a[i++]);

我们希望循环通过在字符串末尾遇到零字符(测试为假)来执行。这需要测试值的预增值以及索引的增值。但不一定按该顺序排序-使用预增量进行编码的方式是:

int i = -1;
do {
  ++i;
  b[i] = a[i];
} while (a[i]);

这是一个更清晰的问题,即使a [i]是一个昂贵的函数或具有副作用,如果该机器具有完整的寄存器,则两个寄存器都应具有相同的执行时间。一个显着的差异可能是索引的退出值。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.