以下是代码:
for(i=0; i<5; i++) {
printf("%d", i);
}
for(i=0; i<5; ++i) {
printf("%d", i);
}
我使用两个“for”循环得到相同的输出,我错过了什么吗?
for(i=0; i<5; i++) {
printf("%d", i);
}
for(i=0; i<5; ++i) {
printf("%d", i);
}
我使用两个“for”循环得到相同的输出,我错过了什么吗?
在评估i++
或++i
之后,i
的新值在两种情况下都相同。 前增和后增的区别在于对表达式本身的评估结果。
++i
递增i
并计算出i
的新值。
i++
计算出i
的旧值,并递增i
。
这在for循环中并不重要的原因是控制流大致按照以下方式工作:
由于(1)和(4)是解耦的,因此可以使用前增或后增。
i++
需要在自增后记住旧值,我认为使用++i
可能更短(可能少1-2条指令)。 - danben很简单。上述的for
循环在语义上等同于
int i = 0;
while(i < 5) {
printf("%d", i);
i++;
}
和
int i = 0;
while(i < 5) {
printf("%d", i);
++i;
}
注意,就这个代码块而言,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++;
}
这是因为在第一个代码块中,j
看到的是 i
增加后的值(i
先被增加,即前缀增加,因此得名),而在第二个代码块中,j
看到的是 i
增加前的值。然而,在编程中,底层实现有所区别:后缀自增运算符i++
需要创建一个临时变量来存储i
的原始值,然后执行自增操作并返回该临时变量。前缀自增运算符++i
不会创建临时变量。当对象是简单类型(如int
)时,任何优化设置都应该能够将其优化掉,但请记住,像迭代器这样更复杂的类中,++运算符被重载了。由于两个重载方法可能有不同的操作(例如,其中一个可能要在stdout输出“嘿,我被前缀自增了!”),如果返回值没有被使用,编译器无法确定这两种方法是否相等(基本上因为这样的编译器将解决无法解决的停机问题),它需要使用更昂贵的后缀自增版本,如果你写myiterator++
。
以下是三个使用前缀自增运算符的理由:
inc *i push *i
,而在另一个情况下,你会使用push *i inc *i
。我不认为这是一种优化的做法。 - MQDuck这是我最喜欢的面试题之一。我将先解释答案,然后告诉您为什么我喜欢这个问题。
解决方案:
答案是两个片段都会打印从0到4(包括4)的数字。这是因为for()
循环通常等同于while()
循环:
for (INITIALIZER; CONDITION; OPERATION) {
do_stuff();
}
可以这样写:
INITIALIZER;
while(CONDITION) {
do_stuff();
OPERATION;
}
你可以看到,在循环结构的底部总是会执行 OPERATION 操作。这种形式下,i++
和 ++i
会产生相同的效果:它们都会递增 i
并忽略结果。新的 i
值直到下一次迭代开始时才被测试,即在循环顶部。
编辑: 感谢 Jason 指出如果循环包含控制语句(如 continue
)会阻止在 while()
循环中执行 OPERATION
,因此此处的for()
和while()
不等价。在 for()
循环中,在下一次迭代之前始终执行 OPERATION
。
为什么这是一个好的面试题目
首先,如果候选人能立即正确回答,只需要花费一两分钟,我们就可以进入下一个问题。
但令我惊讶的是,许多候选人告诉我,使用后置增量的循环将打印从 0 到 4 的数字,而使用前置增量的循环将打印 0 到 5 或 1 到 5。他们通常正确地解释了前缀和后缀递增之间的区别,但是他们误解了 for()
循环的机制。
在这种情况下,我会要求他们使用 while()
重写循环,这真的可以让我了解到他们的思考过程。这也是我问这个问题的原因:我想了解他们解决问题的方式,以及当我对他们的世界观提出质疑时,他们如何继续。
此时,大多数候选人会意识到他们的错误并找到正确的答案。但是我遇到了一个坚持认为自己最初的答案是正确的候选人,然后改变了他将 for()
转换为 while()
的方式。虽然这次面试十分有趣,但我们没有给他发 Offer!
希望这能帮到你!
for
循环不能按照你指定的方式被重写。例如看看 for(int i = 0; i < 42; i++) { printf("%d", i); continue; }
这个例子。你声称它在语义上等同于 int i = 0; while(i < 42) { printf("%d", i); continue; i++; }
,但这显然是错误的。 - jasonfor
循环等同于你和@Adam Liss提供的通用的while
循环。我是说在这个特定的例子中,OP的for
循环和我给出的while
循环之间的语义是相同的。一般来说,从for
循环到while
循环的朴素翻译是有问题的,这就是你陷入麻烦的原因。顺便说一句,注意编译器甚至不需要将OP的for
循环转换为任何类型的循环;例如,编译器可以完全展开循环。 - jason set i = 0
test: if i >= 5 goto done
call printf,"%d",i
set i = i + 1
goto test
done: nop
后缀自增至少需要另外一步操作,但优化起来很容易。
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
for(i=0; i<5; i=j++) {
printf("%d",i);
}
如果像这样编写代码,将会比下面的代码多迭代一次:
for(i=0; i<5; i=++j) {
printf("%d",i);
}
在每次执行printf("%d", i)之后,i++和++i都会被执行,因此它们没有区别。
for (int i = 0; i < 5; cout << "we still not incremented here: " << i << endl, i++)
{
cout << "inside loop body: " << i << endl;
}
是的,你会得到完全相同的输出。为什么你认为它们应该给你不同的输出呢?
后增量或前增量在这种情况下很重要:
int j = ++i;
int k = i++;
f(i++);
g(++i);
for
循环中,你既不赋值也不传递参数,只对它进行递增操作。因此后置和前置这两种运算在这里没有任何意义!