'for'循环内的后递增和预递增产生相同的输出

the*_*ick 188 c c++ for-loop operator-precedence

以下for循环产生相同的结果,即使一个使用后增量和另一个预增量.

这是代码:

for(i=0; i<5; i++) {
    printf("%d", i);
}

for(i=0; i<5; ++i) {
    printf("%d", i);
}
Run Code Online (Sandbox Code Playgroud)

我为两个'for'循环得到了相同的输出.我错过了什么吗?

dan*_*ben 315

在评估i++或之后,两种情况下++i的新值都是i相同的.增量前和增量之间的差异在于评估表达式本身的结果.

++i递增i并计算为的新值i.

i++计算旧的值i,并递增i.

这在for循环中无关紧要的原因是控制流大致如下:

  1. 测试条件
  2. 如果是假,则终止
  3. 如果是真的,执行正文
  4. 执行增量步骤

因为(1)和(4)是去耦的,所以可以使用前增量或后增量.

  • 鉴于`i ++`需要在递增后记住`i`的旧值,我认为`++ i`可能更短(1-2指令的顺序). (61认同)
  • 虽然未使用的值应该被优化掉,对吗? (25认同)
  • @JaminGrey除非你有i ++的理由,否则养成++ i的习惯会有什么影响? (10认同)
  • 当为类调用increment时,它可能变得不等于调用post或pre-increment,因为它的行为可能差别很大. (8认同)
  • Azendale做对了.违约/减少是一种非常不幸的习惯,很多人都有这种习惯.只有在有明确和可辩护的理由时才应使用帖子.当然,在任何非平凡的编译器中,未评估的原语可能没有区别,但这并不意味着post应该是默认选择; 这样的习惯最终只是在惹麻烦. (7认同)
  • 如果C++被称为++ C,最可能的默认习惯是++ i :) (6认同)
  • @JaminGrey可能优化了。但是一旦有人添加了更多使用i的代码(具体取决于上下文和他们的疏忽),就会被引入的一个错误所掩盖。另外,假设您在类上使用该运算符。取决于类,说构造有副作用,但不会被优化。 (2认同)
  • @omatai这很有趣,因为人们通常认为“争论”在这个意义上是指有推理/证据支持的事情。那么,为什么 postinc/decrement 应该是首选默认值的推理/证据在哪里? (2认同)

jas*_*son 114

嗯,这很简单.上述for循环在语义上等同于

int i = 0;
while(i < 5) {
    printf("%d", i);
    i++;
}
Run Code Online (Sandbox Code Playgroud)

int i = 0;
while(i < 5) {
    printf("%d", i);
    ++i;
}
Run Code Online (Sandbox Code Playgroud)

请注意这些行i++;++i;从本块代码的角度来看具有相同的语义.它们对值i(将其递增1)具有相同的效果,因此对这些循环的行为具有相同的影响.

请注意,如果将循环重写为,则会有所不同

int i = 0;
int j = i;
while(j < 5) {
    printf("%d", i);
    j = ++i;
}

int i = 0;
int j = i;
while(j < 5) {
    printf("%d", i);
    j = i++;
}
Run Code Online (Sandbox Code Playgroud)

这是因为在第一个代码块中j看到i增量之后的值(i首先递增,或者预先递增,因此名称),并且在第二个代码块中j看到i增量之前的值.

  • 很好的答案解释当different可以发生!Upvote :) (4认同)

And*_*ist 92

您的代码的结果将是相同的.原因是两个增量操作可以看作是两个不同的函数调用.这两个函数都会导致变量递增,只有它们的返回值不同.在这种情况下,返回值只是丢弃,这意味着输出中没有可区别的差异.

然而,在引擎盖下有一个区别:后增量i++需要创建一个临时变量来存储原始值i,然后执行增量并返回临时变量.预增量++i不会创建临时变量.当然,任何体面的优化设置都应该能够在对象简单的情况下优化它int,但请记住,++ - 运算符在迭代器等更复杂的类中被重载.由于两个重载方法可能具有不同的操作(例如,可能希望输出"嘿,我是预先递增的!"到stdout),当不使用返回值时,编译器无法判断方法是否等效(基本上因为这样的编译器可以解决无法解决的暂停问题),如果你写的话,它需要使用更昂贵的后增量版本myiterator++.

你应该预先增加的三个原因:

  1. 您不必考虑变量/对象是否可能具有重载的增量后方法(例如在模板函数中)并以不同方式对待它(或者忘记以不同方式对待它).
  2. 一致的代码看起来更好.
  3. 当有人问你"你为什么预先增加?" 您将有机会向他们讲述编译器优化的暂停问题和理论限制.:)

  • 他没错.当您不使用副本时,后递增基元可以很容易地被编译器优化.但他最后一点暗示的一点是迭代器的后递增是作为方法调用实现的,并构造了一个全新的迭代器副本.这些不能被编译器优化,因为构造函数和函数调用可能具有编译器无法跟踪的副作用,因此不能认为不重要. (18认同)
  • 他错了:我在这里尝试了编译器资源管理器上的预增量和后增量:https://godbolt.org/,无论您采用哪种增量方式,您都会得到完全相同的程序集。对于 C,你只是描述你希望编译器发生什么,如果它相当于相同的事情,那么你通常会得到相同的输出,现在的编译器非常聪明。前后增量几乎不会产生任何区别 - 如果您不相信我,请尝试编译器资源管理器! (3认同)

Ada*_*iss 23

这是我最喜欢的面试问题之一.我先解释答案,然后告诉你为什么我喜欢这个问题.

解:

答案是两个片段都打印从0到4的数字,包括0和4.这是因为for()循环通常等同于while()循环:

for (INITIALIZER; CONDITION; OPERATION) {
    do_stuff();
}
Run Code Online (Sandbox Code Playgroud)

可写:

INITIALIZER;
while(CONDITION) {
    do_stuff();
    OPERATION;
}
Run Code Online (Sandbox Code Playgroud)

您可以看到OPERATION 始终在循环的底部完成.在这种形式中,应该清楚i++并且++i将具有相同的效果:它们都会增加i并忽略结果.i直到下一次迭代开始时,才会在循环的顶部测试新值.


编辑:感谢贾森指出,这for()while()等价并没有如果循环包含控制语句(如持有continue),这将防止OPERATION从一个执行while()循环. OPERATION永远只是一个下一次迭代之前执行for()循环.


为什么这是一个很好的面试问题

首先,如果候选人立即告诉正确答案,则只需一两分钟,因此我们可以直接进入下一个问题.

但令人惊讶的是(对我来说),许多候选人告诉我,后增量的循环将打印从0到4的数字,预增量循环将打印0到5,或1到5.他们通常解释之间的区别前后增量正确,但他们误解了for()循环的机制.

在这种情况下,我要求他们使用重写循环while(),这确实让我对他们的思维过程有了一个很好的了解.这就是我首先提出这个问题的原因:我想知道他们如何解决问题,以及当我对他们的世界运作方式产生怀疑时他们如何处理.

此时,大多数候选人意识到他们的错误并找到正确的答案.但我有一个谁坚持他原来的答案是正确的,那么改变他翻译的方式for()while().这是一次精彩的采访,但我们没有提出要约!

希望有所帮助!

  • 好吧,你应该重新考虑一下这个面试问题,因为你犯了一个严重但常见的错误.一般来说,`for`循环不能被重写.看看`for(int i = 0; i <42; i ++){printf("%d",i); 继续; 例如.你的主张是它在语义上等同于`int i = 0; while(i <42){printf("%d",i); 继续; 我++; 这显然是错的. (18认同)
  • @Jason:今天我学到了我没想过的边缘案例!在将近十年的时间里,我有一个候选人认识到了这一点.如果我继续使用这个因我提到的原因而有价值的问题,我肯定会根据上面的编辑对其进行重新修改.如果候选人立即提供正确的答案,我会问是否有任何例外.:-) +1为您明确的反例.谢谢! (5认同)

tva*_*son 6

因为在任何一种情况下,增量都是在循环体之后完成的,因此不会影响循环的任何计算.如果编译器是愚蠢的,使用后增量可能效率稍低(因为通常它需要保留pre值的副本供以后使用),但我希望在这种情况下可以优化任何差异.

考虑如何实现for循环可能很方便,基本上可以转换为一组赋值,测试和分支指令.在伪代码中,预增量看起来像:

      set i = 0
test: if i >= 5 goto done
      call printf,"%d",i
      set i = i + 1
      goto test
done: nop
Run Code Online (Sandbox Code Playgroud)

后增量至少会有另一个步骤,但优化之后将是微不足道的

      set i = 0
test: if i >= 5 goto done
      call printf,"%d",i
      set j = i   // store value of i for later increment
      set i = j + 1  // oops, we're incrementing right-away
      goto test
done: nop
Run Code Online (Sandbox Code Playgroud)


iss*_*s42 5

如果你这样写,那就很重要了:

for(i=0; i<5; i=j++) {
    printf("%d",i);
}
Run Code Online (Sandbox Code Playgroud)

比这样写会迭代一次:

for(i=0; i<5; i=++j) {
    printf("%d",i);
}
Run Code Online (Sandbox Code Playgroud)