13

我在 Knuth 的“计算机编程艺术”的评论中阅读了以下内容:

“非常‘实用性’意味着想成为 CS 专业的学生必须学习 Kernighan 在设计 C 时的错误,尤其是臭名昭著的事实,即 for 循环反复评估 for 条件,它重复 while 并且无法匹配大多数其他语言的行为它实现了一个 for 循环。”

http://www.amazon.com/review/R9OVJAJQCP78N/ref=cm_cr_pr_viewpnt#R9OVJAJQCP78N

这家伙在说什么?你怎么能实现一个 for 循环,而不仅仅是 for 循环的语法糖?

4

11 回答 11

29

考虑一下:

for i:=0 to 100 do { ... }

在这种情况下,我们可以通过函数调用替换最终值 100:

for i:=0 to final_value() do { ... }

...并且final_value-function 只会被调用一次。

然而,在 C 中:

for (int i=0; i<final_value(); ++i) // ...

... final_value- 函数将在循环中的每次迭代中被调用,因此更详细的做法是一个好习惯:

int end = final_value();
for (int i=0; i<end; ++i) // ...
于 2008-10-23T13:26:00.440 回答
5

如果你想要的只是一个简单的计数循环,那么

for (i=0; i<100; i++) dostuff();

会好的,编译器可以优化它。

如果您在 for 语句的 continue 部分使用函数,例如

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 被称为每个循环。

我编写了较低级别的函数,因此它们可以在这样的 for 循环中使用。它将 while 循环的所有元素组合在一起,因此您可以更轻松地理解它。

于 2008-10-23T14:21:37.520 回答
4

他可能指的for i:=0 to N是迭代集合元素的 for 循环和 for-each 循环。我怀疑所有具有 C 风格 for 循环的语言实际上都是从 C 中获得的。

于 2008-10-23T13:22:46.953 回答
3

Magnus 说得对,但还应注意,在大多数语言(C 之前)中,条件是结束标准(即“当 i 等于 100 时停止”)。在 C(和大多数后 C 语言)中,它是继续标准(即“在 i 小于 100 时继续”)。

于 2008-10-23T13:34:46.147 回答
2

也许循环展开?如果您知道 for 循环将执行多少次,您可以从字面上复制并粘贴循环的内容。大多数 while 循环将基于某些条件,而不是从 0 到 N 的简单计数,因此将无法使用此优化。

举这个例子

int x;
for (x = 10; x != 0; --x)
{
    printf ("Hello\n");
}

我知道你通常会这样做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"

我们不需要任何其他指令,所以它更快。这只是一个简单的例子——我会努力找到一个更好的!

于 2008-10-23T13:32:47.087 回答
2

基本上,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循环主要用于运行提前知道多少次,你仍然可以使用breakto 提前终止,所以 stop term 的动态评估比有用更烦人:如果你真的需要动态评估,你使用while () {}(or do {} while ())。

在其他一些语言上,比如 Lua,停止条件只在循环初始化时计算一次。它有助于预测循环行为,并且通常性能更高。

于 2008-10-23T13:35:17.787 回答
2

在 x86 上,for 循环可以在汇编级别完成,而无需进行 while 循环。循环指令减小寄存器 ecx的值,如果 ecx 大于 0,则跳转到操作数,否则不执行任何操作。这是一个经典的 for 循环。

mov ecx, 0x00000010h
loop_start:
;loop body
loop loop_start
;end of loop
于 2008-10-23T13:38:04.097 回答
2

在 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 个内容副本替换整个循环,无论这些内容是什么。AC 优化器必须检查和分析内容。

这实际上只是 C 语法设计阻碍编译器的众多方式之一。

于 2008-10-23T14:22:24.133 回答
1

这些语言纯粹主义者似乎从未意识到的是 C 的全部意义,在某种程度上,C++ 提供了实现您想要的东西以及如何实现它的可能性。现在诚然,如果您的结束表达式的结果发生变化,最好只使用一个while循环。也许问题在于程序员得到的印象是 C 实现了“高级”,但每个人都应该知道 C 是一种相当低级的语言。

于 2008-10-23T14:45:50.093 回答
1

非常“实用”意味着想成为 CS 专业的学生必须学习 Kernighan 在设计 C 时的错误,尤其是臭名昭著的事实,即 for 循环重复评估 for 条件,它重复 while 并且无法匹配大多数其他语言的行为实现一个for循环。

啊哈哈哈哈!

我喜欢审稿人的基本(双关语)断言:

  • 克尼汉的错误
  • 臭名昭著的事实
  • 无法匹配大多数其他语言的行为

对我来说,它闻起来像是从未成功掌握 C 的基本特征/哲学的人。

介绍

当我还在大学学习物理时,当我发现 C 的for循环时,我发现 C(即类 C 语言)将成为我的首选语言。

显然,一个人的风格失败就是另一个人的风格成功。这将结束本次讨论的主观部分。

也许 Kernigan 的 for 应该被称为循环?

只是在开玩笑?也许不是。

该评论的作者显然决定每种语言结构都应该具有跨语言的相同行为。因此,将 for as 重命名循环缓解他/她的不适。

问题是作者未能理解 C 的for是 while 的扩展,不是不同的循环。这意味着while是 for 的语法糖轻版本而不是for可能已被阉割的while其他语言。

真的需要C吗?

引用评论的作者,他/她对forwhile做出以下断言:

  • for用于常量循环,从开始到结束
  • while用于在每次迭代时评估条件的循环

当您可以组合它们时,拥有正交特征是一件好事。但是在我知道的所有语言中,没有办法将forwhile结合在一起。

示例:如果对于审阅者的语言,您需要一个从头到尾的循环(例如for),但仍然能够在每次循环迭代时进行评估(例如while):您可能需要在子集中查找一个容器(不是整个容器),根据一些有状态测试的值?

在类 C 语言中,您使用for。在审阅者的理想语言中,你只是,好吧,破解一些丑陋的代码,因为你没有for_while循环(或 C 的for循环)而哭泣。

结论

我相信我们可以用以下陈述来总结审稿人对 C(和类 C 语言)的批评“ Kernigan 的臭名昭著的错误,即没有给 C 提供以前现有语言的语法”,这可以再次概括为“从不认为不同” .

引用 Bjarne Stroustrup 的话:

只有两种语言:人们抱怨的语言和没人使用的语言。

所以,总而言之,审稿人的评论应该算是一种表扬。

^_^

于 2008-10-24T08:28:52.020 回答
0

也许 Knuth 指的是 BASIC。

在 BASIC(较旧的,我不了解 VB)中,FOR 循环的实现方式不同。

例如循环十次

对于 N=1 到 10

...

下一个

---- OR ---- 求小于 100 的偶数之和

对于 N=0 到 100 第 2 步

...

下一个

在 C 中,您可能在“for”中有任何条件来检查是否退出循环。更灵活,我想。

于 2008-10-23T14:34:46.293 回答