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

123

我目前正在学习C++,之前学过递增运算。我知道你可以使用"++x"来使递增在之前进行,而使用"x++"则是在之后进行。

不过,我真的不知道何时应该使用其中的任何一个...到目前为止,我从来没有真正使用过"++x",并且事情一直都很顺利 - 那么,在什么情况下应该使用它呢?

例如:在for循环中,何时最好使用"++x"?

另外,有人能够解释一下不同的递增(或递减)方式吗?我会非常感激。

12个回答

165

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

x++ 会在处理当前语句之后增加变量 x 的值。

++x 会在处理当前语句之前增加变量 x 的值。

因此,只需根据您编写的逻辑来决定。

x += ++i 将会增加 i 的值并将 i+1 加到 x 中。 x += i++ 将会将 i 加到 x 中,然后再增加 i 的值。


34
请注意,在for循环中,对于原始数据类型,完全没有任何区别。许多编码风格建议在可能被误解的情况下永远不要使用增量运算符;即x++或++x应该只存在于自己的一行,永远不要写成y=x++。个人而言,我不喜欢这种做法,但这比较少见。 - Mikeage
2
如果单独使用该行,生成的代码几乎肯定是相同的。 - Nosredna
15
在C++中,x++可能看起来很学究(因为它确实是 :)),但它实际上是一个右值,其值为增量前的x,而++x是一个左值,其值为增量后的x。这两个表达式都不能保证何时实际的增量值被存回x中,只能保证在下一个顺序点之前发生。“在处理当前语句之后”并不严格准确,因为某些表达式具有顺序点,而某些语句则是复合语句。 - CB Bailey
10
实际上,这个答案有误导性。在实践中,修改变量x的时间点可能没有区别。不同之处在于x++被定义为返回x的先前值的rvalue,而++x仍然是指向变量x。 - sellibitze
5
答案中暗示了一个不存在的顺序。标准中没有说明增量何时发生。编译器有权将"x += i++"实现为:"int j = i; i=i+1 ; x += j;"(即‘i’在“处理当前语句”之前递增)。这就是为什么“i = i++”具有未定义行为,也是我认为答案需要“调整”的原因。对于“x += ++i”的描述是正确的,因为没有任何顺序的建议:“将递增i并将i + 1添加到x”。 - Richard Corden
显示剩余6条评论

59

Scott Meyers建议您在逻辑不需要后缀表达式的情况下,应该优先考虑前缀表达式。

《More Effective C++》第6条——对我来说这已经足够权威了。

对于那些没有拥有这本书的人,在这里摘录相关引用。 在第32页:

从您作为C程序员的日子起,您可能会想起增量运算符的前缀形式有时被称为“递增和获取”,而后缀形式则通常被称为“获取和递增”。 这两个短语很重要,因为它们几乎可以充当正式的规范……

在第34页上:

如果你是一个关注效率的人,你可能在第一次看到后缀递增函数时就开始出汗了。 那个函数必须为其返回值创建临时对象,而上面的实现还创建了一个显式的临时对象,必须构造和销毁。 前缀递增函数没有这样的临时变量……


4
如果编译器没有意识到增量前的值是不必要的,它可能会用几条指令来实现后缀增量-复制旧值,然后再进行增量。而前缀增量应该总是只需一条指令。 - gnud
12
昨天我恰好使用gcc进行了测试:在一个for循环中,当值在执行“i++”或“++i”后被丢弃时,生成的代码是相同的。 - Giorgio
在for循环之外尝试它。赋值的行为必须不同。 - duffymo
1
我明确不同意Scott Meyers的第二点 - 它通常是无关紧要的,因为90%或更多的"x++"或"++x"情况通常与任何赋值隔离开来,并且优化器足够聪明,以识别在这种情况下不需要创建任何临时变量。在这种情况下,这两种形式是完全可互换的。这意味着充满“x ++”的旧代码库应该保持原样 - 你更有可能在将它们更改为“++x”时引入微妙的错误,而不是在任何地方提高性能。可以说最好使用“x ++”,让人们思考。 - omatai
6
你可以信任Scott Meyers的观点,但是如果你的代码对性能要求如此严苛,以至于++xx++之间的任何性能差异都会对结果产生影响,那么更重要的是你应该使用一款完全且正确优化两个版本的编译器,而不论上下文环境如何。用“由于我只有这把旧钉子锤,所以只能以43.7度的角度将钉子钉进去”来为建造房屋辩护是一个荒谬的论据。使用更好的工具是正确的选择。 - Andrew Henle

35

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

8

在我看来,需要记住的最重要的一点是,x++ 需要返回实际增量发生之前的值--因此,它必须制作一个对象的临时副本(前缀增量)。这比 ++x 更不有效率,++x 可以原地递增并返回。

另外值得一提的是,大多数编译器在可能的情况下都能够优化掉这种不必要的操作,例如以下两个选项将导致相同的代码:

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

临时副本对于我们在循环中使用它来递增变量的情况也是如此吗?例如,对于int m=0; for (int i(0);i<10;++i) m++;int m=0; for (int i(0);i<10;++i) ++m;之间的m有什么区别吗? - Isin Altinkaya

5

我同意@BeowulfOF的观点,不过为了更清晰明确,我总是建议将语句分开,这样逻辑就会非常清楚,例如:

i++;
x += i;

或者

x += i;
i++;

因此,我的答案是如果你编写清晰的代码,那么这应该很少有影响(如果有影响,则说明您的代码可能不够清晰)。

5

如果 count{5};

如果你使用 ++count,它将在语句之前被处理。

total = --count +6;

总数将等于10。

如果您使用count ++,则该语句之后将进行处理。

total = count-- +6;

总计将等于11


为什么“after”语句有所不同?5 + 6将是11,然后如果--生效,总数仍然是10。正确吗? - Kush
2
total = --count + 6 相当于 count = count - 1; total = count + 6; 而 while total = count-- + 6 则相当于 total = count + 6; count = count - 1; 语句顺序不同,结果也不同。 - VdesmedT

2

++,--操作符的后缀形式遵循先使用再修改的规则,

前缀形式(++x,--x)遵循先修改再使用的规则。

示例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
  • 注意:不建议在同一表达式中多次使用增量/减量运算符来操作同一个变量。这种表达式的处理/结果因编译器而异。

1
不是包含多个递增/递减操作的表达式“因编译器而异”,而更糟糕的是:在序列点之间进行多个修改具有未定义行为,会使程序出现问题。 - underscore_d

2

想再强调一下,++x比x++更,尤其是当x是任意类型的对象时,所以除非出于逻辑原因,应该使用++x。


2
我只想强调这种强调很可能是误导性的。如果你看到一些以孤立的“x++”结束的循环,并认为“啊哈!-这就是运行缓慢的原因!”并将其更改为“++x”,那么请预期没有任何区别。优化器足够聪明,能够识别出当没有人使用它们的结果时,不需要创建任何临时变量。这意味着充满“x++”的旧代码库应该被保留——你更有可能在更改它们时引入错误,而不是在任何地方提高性能。 - omatai

1

您要求一个例子:

这个(order是一个std::vector)将会在i == order.size()-1时,在访问order[i].size()时崩溃:

while(i++ < order.size() && order[i].size() > currLvl);

这段代码不会在 order[i].size() 处崩溃,因为 i 会被递增、检查并退出循环:

while(++i < order.size() && order[i].size() > currLvl);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接