我在Knuth的"计算机编程的艺术"的评论中读到了以下内容:
"非常'实用性'意味着未来的CS专业必须学习Kernighan在设计C时的错误,特别是一个臭名昭著的事实,即for循环反复评估for条件,它重复while并且无法匹配大多数实现for循环的其他语言的行为。
(http://www.amazon.com/review/R9OVJAJQCP78N/ref=cm_cr_pr_viewpnt#R9OVJAJQCP78N(
这家伙在说什么? 你怎么能实现一个 for 循环,而不仅仅是一段时间的语法糖循环?
考虑一下:
for i:=0 to 100 do { ... }
在这种情况下,我们可以用函数调用替换最终值 100:
for i:=0 to final_value() do { ... }
。并且 final_value
函数只会被调用一次。
然而,在 C 中:
for (int i=0; i<final_value(); ++i) // ...
。每次循环迭代都会调用 final_value
-函数,因此最好更详细:
int end = final_value();
for (int i=0; i<end; ++i) // ...
如果您想要的只是一个简单的计数循环,那么
for (i=0; i<100; i++) dostuff();
会没事的,编译器可以优化它。
如果您在 for 语句的继续部分中使用函数,例如
for (i=0; i<strlen(s); i++) dostuff();
然后每次都会评估函数,这通常不是一个好主意,因为函数开销会减慢您的进程。有时它会减慢您的流程,直至无法使用。
如果函数的返回值在迭代期间不会更改,请从循环中提取它:
slen = strlen(s);
for (i=0; i<slen; i++) dostuff();
但有时函数每次调用都会返回不同的值,然后你不希望它从循环中提取:
for (isread(fd, &buffer, ISFIRST);
isstat(fd) >= 0;
isread(fd, &buffer, ISNEXT)
{
dostuff(buffer);
}
并且您希望每次都对其进行评估。(这是一个基于我所做的工作略微做作的例子,但它显示了潜力(。
C 为您提供了以任何方式滚动循环的原始能力。你必须知道你的循环应该如何工作,并根据你的需要尽可能地优化它。
最后一个例子可以表示为一个while循环:
isread(fd, &buffer, ISFIRST);
while (isstat(fd) >= 0)
{
dostuff(buffer);
isread(fd, &buffer, ISNEXT);
}
但它没有那么整洁,如果我在循环中使用 continue,那么我必须再次调用迭代 isread。将整个事情放在一个 for 循环中会使其更整洁,并确保迭代 isread 被调用每个循环。
我编写了较低级别的函数,以便它们可以用于这样的循环。它将 while 循环的所有元素组合在一起,以便您可以更轻松地理解它。
他可能指的是像 for i:=0 to N
这样的循环和迭代集合元素的 for-each 循环。我怀疑所有具有 C 样式 for 循环的语言实际上都是从 C 中获取的。
Magnus 说得对,但也应该注意,在大多数语言(pre-C(中,条件是结束标准(即"当我等于 100 时停止"(。 在 C(以及大多数后 C 语言(中,它是继续标准(即"在 i 小于 100 时继续"(。
基本上,C(以及Java,JavaScript和许多C派生语言(for
循环确实是while
循环的语法糖:
for (int i = 0; i < max; i++) { DoStuff(); }
这是一个非常流行的成语,严格等同于:
int i = 0; while (i < max) { DoStuff(); i++; }
(将范围问题分开,无论如何,它们在 C 版本之间发生了变化。
在每次迭代时评估停止条件。在某些情况下,它可能很有趣,但它可能是一个陷阱:我在源代码中看到i < strlen(longConstantString)
,这是减慢程序速度的主要方法,因为strlen
在 C 中是一个昂贵的函数。
不知何故,for
循环大多设计为运行多次事先知道,你仍然可以使用 break
提前终止,所以停止项的动态求值比有用更烦人:如果你真的需要动态求值,你用while () {}
(或do {} while ()
(。
在其他一些语言上,如Lua,停止条件只在循环初始化时计算一次。它有助于预测循环行为,并且通常性能更高。
在 x86 上,for 循环可以在程序集级别完成,而无需进行 while 循环。循环指令减小寄存器 ecx 的值,并在 ecx 大于 0 时跳转到操作数,否则它不执行任何操作。这是一个经典的循环。
mov ecx, 0x00000010h
loop_start:
;loop body
loop loop_start
;end of loop
循环展开?如果您知道 for 循环将执行多少次,则可以从字面上复制并粘贴循环的内容。大多数 while 循环将基于某些条件,这些条件不是从 0 到 N 的简单计数,因此无法使用此优化。
以这个例子为例
int x;
for (x = 10; x != 0; --x)
{
printf ("Hellon");
}
我知道你通常会做x = 0; x <= 10; ++x
,但一切都会在大会上揭示。
一些伪程序集:
mov 10, eax
loop:
print "hello"
dec eax
jne loop
在这个例子中,我们不断跳回循环以打印"hello"10次。但是,我们每次循环都使用jne
指令评估条件。
如果我们展开它,我们可以简单地把
print "hello"
print "hello"
print "hello"
print "hello"
print "hello"
print "hello"
print "hello"
print "hello"
print "hello"
print "hello"
我们不需要任何其他指令,所以它更快。这只是一个简单的例子 - 我会尝试找到一个更好的!
在 Ada 中(我相信大多数其他 Algol 派生的语言(,"for"循环的终止条件只在循环开始时计算一次。例如,假设您在 Ada 中有以下代码
q := 10;
for i in 1..q loop
q := 20;
--// Do some stuff
end loop;
此循环将正好迭代 10 次,因为循环开始时 q 为 10。但是,如果我用 C 编写看似等效的循环:
q = 10;
for (int i=0;i<q;i++) {
q = 20;
// Do some stuff
}
然后循环迭代 20 次,因为当我变得足够大以至于它很重要时,q 已更改为 20。
当然,C方式更灵活。然而,这有相当多的负面影响。显而易见的是,程序必须浪费精力在每个周期重新检查循环条件。一个好的优化器可能足够聪明,可以在这样的简单情况下解决问题,但是如果"q"是全局的并且"做一些事情"包括过程调用(因此理论上可以修改q(会发生什么?
残酷的事实是,我们对 Ada 循环的了解比对 C 循环的了解要多得多。这意味着,在优化器中具有相同水平的智能和努力的情况下,Ada 可以在优化方面做得更好。例如,Ada 编译器知道它可以用 10 个内容副本替换整个循环,无论这些内容是什么。C 优化器必须检查和分析内容。
这实际上只是 C 语法设计阻碍编译器的众多方式之一。
这些语言纯粹主义者似乎从未意识到的是 C 的全部意义,在某种程度上C++,是提供了实现你想要的东西和你想要的方式的可能性。现在诚然,如果结束表达式的结果发生变化,最好只使用 while 循环。也许问题是程序员得到的印象是C实现了"高级",但每个人都应该知道C是一种相当低级的语言。
呜非常"实用性"意味着未来的CS专业必须学习Kernighan在设计C时的错误,特别是一个臭名昭著的事实,即for循环反复评估for条件,它重复while并且无法匹配大多数实现for循环的其他语言的行为。
呜哈!
我喜欢审稿人的基本(双关语(断言:
- 克尼根的错误
- 臭名昭著的事实
- 无法匹配大多数其他语言的行为
对我来说,这闻起来像是一个从未成功掌握C的基本特征/哲学的人。
介绍
当我还在大学学习物理时,当我发现C的for loop时,我发现C(即类C语言(成为我选择的语言。
所以显然,一个人的风格失败就是另一个人的风格成功。讨论的主观部分到此结束。
也许克尼根的 for 应该被称为循环?
开玩笑?也许不是。
该评论的作者显然决定每种语言结构应该在语言中具有相同的行为。因此,重命名为循环会缓解他/她的不适。
问题是作者无法理解C的for是while的扩展,而不是不同的循环。这意味着 while 是 for 的句法糖轻版本,而不是 while 其他语言,其中 for 可能已被阉割。
真的需要C吗?
引用评论的作者的话,他/她对 for 和 while 做出了以下断言:
- for 用于常量循环,从开始到结束
- while for 循环在每次迭代时评估条件
当您可以将正交要素组合在一起时,具有正交要素是一件好事。但是在我所知道的所有语言中,没有办法将 for 和 while 结合在一起。
示例:如果对于审阅者的语言,您需要一个从开始到结束的循环(如 for(,但仍然能够在每次循环迭代(如一段时间(进行计算:您可能需要在容器的子集(而不是整个容器(中找到一个值根据某个有状态测试?
在类似 C 的语言中,您使用 for。在审稿人的理想语言中,你只是,好吧,破解一些丑陋的代码,哭泣,因为你没有for_while循环(或C的循环(。
结论
我相信我们可以用以下陈述来总结审稿人对C(和类C语言(的批评">Kernigan臭名昭著的错误,即没有给C提供以前现有语言的语法",这可以再次总结为">永远不要思考不同"。
引用Bjarne Stroustrup的话:
因此,总而言之只有两种语言:人们抱怨的语言和没有人使用的语言。
,评论者的评论应被视为一种赞美。
^_^
也许高德纳指的是BASIC。
在 BASIC(较旧的,我不知道 VB(中,FOR 循环的实现方式不同。
例如 循环十次
对于 N=1 到 10
。
下一个 N
----或----查找小于 100 的偶数之和
对于 N=0 到 100 步骤 2
。
下一个 N
在 C 中,您可能在"for"中有任何条件来检查是否退出循环。 我认为更灵活。