'do … while'与'while'

可能重复:
虽然与做同时
什么时候应该使用do-while而不是while循环?

我已经编程了一段时间(2年工作+ 4.5年学位+ 1年大学预科),而且我从来没有使用过编程入门课程中被迫使用的do-while循环。 我越来越觉得,如果我从来没有碰到如此基本的东西,那么我在做编程错误。

难道我只是没有遇到正确的情况?

有什么样的例子需要使用do-while而不是一段时间?

(我的学校几乎都是用C / C ++编写的,而且我的工作是用C#编写的,所以如果有另外一种语言,那么绝对有意义,因为生存时间的工作方式不同,那么这些问题就不适用了。

澄清…我知道一段while和一段while的区别。 在检查退出条件,然后执行任务。 do-while执行任务,然后检查退出条件。

如果你总是希望循环至less执行一次。 这并不常见,但是我经常使用它。 您可能想要使用它的一种情况是尝试访问可能需要重试的资源,例如

 do { try to access resource... put up message box with retry option } while (user says retry); 

如果编译器不能胜任优化,do-while会更好。 do-while只有一个条件跳转,而while和while则有条件跳转和无条件跳转。 对于stream水线式的CPU并不执行分支预测,这可以在紧密循环的性能上产生很大的差异。

另外,由于大多数编译器都足够聪明地执行这种优化,所以在反编译代码中发现的所有循环通常都是do-while(如果反编译器甚至困扰从本地gotos重build循环)。

当你想要至less执行一次的时候,这样做是有用的。 至于使用do while while while的一个很好的例子,可以说你想做出以下几点:一个计算器。

你可以通过使用一个循环并在每次计算后检查这个人是否想要退出程序。 现在你可以假设一旦程序被打开,这个人至less要这样做一次,所以你可以这样做:

 do { //do calculator logic here //prompt user for continue here } while(cont==true);//cont is short for continue 

我在TryDeleteDirectory函数中使用了这个。 这是这样的

 do { try { DisableReadOnly(directory); directory.Delete(true); } catch (Exception) { retryDeleteDirectoryCount++; } } while (Directory.Exists(fullPath) && retryDeleteDirectoryCount < 4); 

这是一个间接的答案,但这个问题让我想到了它背后的逻辑,我认为这可能是值得分享的。

正如其他人所说的,当你想要至less执行一次主体时,你使用了do ... while循环。 但是在什么情况下你想这样做呢?

那么,我能想到的最明显的情况是,当检查条件的初始值(“未清除”)与您想要退出时的值相同时 。 这意味着您需要执行一次循环体来将条件填充到一个不存在的值,然后根据该条件执行实际的重复。 程序员如此懒惰,有人决定把它包装在一个控制结构中。

因此,例如,从超时的串口读取字符可能采取以下forms(在Python中):

 response_buffer = [] char_read = port.read(1) while char_read: response_buffer.append(char_read) char_read = port.read(1) # When there's nothing to read after 1s, there is no more data response = ''.join(response_buffer) 

注意代码的重复: char_read = port.read(1) 。 如果Python有一个do ... while循环,我可能会使用:

 do: char_read = port.read(1) response_buffer.append(char_read) while char_read 

创build循环的新范围的语言的附加好处: char_read不会污染函数名称空间。 但是请注意,还有一个更好的方法可以做到这一点,那就是使用Python的None值:

 response_buffer = [] char_read = None while char_read != '': char_read = port.read(1) response_buffer.append(char_read) response = ''.join(response_buffer) 

所以这里是我的观点的症结所在:在可空types的语言中, initial_value == exit_value的情况出现得less得多, 可能就是为什么你没有遇到它。 我并不是说永远不会发生这种情况,因为有时函数返回None来表示一个有效的条件。 但是在我的匆忙和简短的意见,如果你使用的语言不允许一个值表示:这个variables尚未初始化,这将发生更多的事情。

这不是完美的推理:现实中,现在空值是常见的,它们只是形成variables可以采用的有效值集合的一个元素。 但实际上,程序员有一种方法来区分处于合理状态的variables,其中可能包括循环退出状态,并处于未初始化状态。

我在学校时使用了一些,但从那以后就没那么多了。

从理论上讲,当你希望循环体在退出条件检查之前执行一次时,它们是有用的。 问题是,对于我不想检查的less数情况, 通常我希望在循环体中间进行退出检查,而不是在最后。 在这种情况下,我更喜欢用for (;;)if (condition) exit;来使用知名的if (condition) exit; 在身体某处。

实际上,如果我在循环退出条件上有些不稳定,有时候我觉得在需要的地方用一个退出语句开始将循环写为for (;;) {}是有用的,然后当我完成时可以通过移动初始化,退出条件和/或在括号内增加代码来查看是否可以“清理”它。

一种情况,你总是需要运行一段代码,并根据其结果,可能更多的时间。 同样可以用一个常规的while循环来产生。

 rc = get_something(); while (rc == wrong_stuff) { rc = get_something(); } do { rc = get_something(); } while (rc == wrong_stuff); 

就这么简单:

先决条件vs后置条件

  • while(cond){…} – 前提条件,它只在检查后执行代码。
  • 做{…} while(cond) – 后置条件,代码至less执行一次。

现在你知道秘密..明智地使用它们:)

如果你想运行代码块至less一次。 while另一方面则不会总是按照指定的标准运行。

我看到这个问题已经得到了充分的回答,但是想要添加这个非常具体的用例场景。 你可能会更频繁地开始使用do …

 do { ... } while (0) 

通常用于多行的#defines。 例如:

 #define compute_values \ area = pi * r * r; \ volume = area * h 

这工作正常:

 r = 4; h = 3; compute_values; 

– 但是,有一个陷阱:

 if (shape == circle) compute_values; 

当这扩展到:

 if (shape == circle) area = pi *r * r; volume = area * h; 

如果你把它包装在一个do … while(0)循环中,它可以正确地展开为一个块:

 if (shape == circle) do { area = pi * r * r; volume = area * h; } while (0); 

到目前为止的答案总结了一般用途的做法。 但OP要求一个例子,所以这里是一个:获取用户input。 但用户的input可能是无效的 – 所以你要求input,validation它,如果有效,继续,否则重复。

使用do-while,当input无效时,您将获得input。 用一个常规的while循环,你得到一次input,但如果它是无效的,你一次又一次地得到它,直到它是有效的。 不难看出,前者更短,更优雅,更容易维护,如果身体的循环变得更加复杂。

我用它来读取多次读取相同结构的读取器。

 using(IDataReader reader = connection.ExecuteReader()) { do { while(reader.Read()) { //Read record } } while(reader.NextResult()); } 

下面是我的理论,为什么大多数人(包括我)更喜欢while(){}循环来做while():while(){}循环可以很容易地被调整为像do..while()是不正确的。 while循环以某种方式“更一般”。 另外程序员喜欢容易掌握的模式。 一个while循环在开始时说明了它的不变性,这是一件好事。

这就是我所说的“更一般”的意思。 采取这个做… while循环:

 do { A; if (condition) INV=false; B; } while(INV); 

将其转换为while循环很简单:

 INV=true; while(INV) { A; if (condition) INV=false; B; } 

现在,我们在while循环中使用一个模型:

 while(INV) { A; if (condition) INV=false; B; } 

把它变成一个do..while循环,产生这个怪物:

 if (INV) { do { A; if (condition) INV=false; B; } while(INV) } 

现在我们在两端进行两次检查,如果不变,你必须在两个地方进行更新。 在某种程度上do..while就像工具箱中的专用螺丝刀,你永远不会使用,因为标准螺丝刀可以做你需要的一切。

我正在编程大约12年,仅在3个月前,我遇到了使用do-while的真正方便的情况,因为在检查条件之前总是需要迭代。 所以猜你的大时间是前进:)。

我无法想象如果你没有使用do...while循环,你已经这么久了。

现在有另一台显示器上有一个这样的循环,该程序中有多个这样的循环。 他们都是这样的forms:

 do { GetProspectiveResult(); } while (!ProspectIsGood()); 

while循环while循环之前检查条件, do...while循环do...while循环之后检查条件。 这是有用的是,你想基于循环运行的副作用条件,或者像其他海报说,如果你想循环运行至less一次。

我明白你来自哪里,但是这种do-while是很less使用的,而且我从来没有用过自己。 你不是做错了。

你不是做错了。 这就像说某人做错了,因为他们从来没有使用过byte原语。 这只是不常用。

当我在一个文件的开头读取一个定点值的时候,我已经使用了一个do while ,但是除此之外,我认为这个结构并不常用,这是不正常的。真的情况。

 -- file -- 5 Joe Bob Jake Sarah Sue -- code -- int MAX; int count = 0; do { MAX = a.readLine(); k[count] = a.readLine(); count++; } while(count <= MAX) 

我使用do / while循环的最常见的场景是在一个基于某些input的小控制台程序中运行,并且会按照用户喜欢的次数重复执行。 显然,一个控制台程序运行没有任何意义, 但超越第一次是由用户来决定的,因此不是仅仅是while

如果需要,这允许用户尝试一堆不同的input。

 do { int input = GetInt("Enter any integer"); // Do something with input. } while (GetBool("Go again?")); 

我怀疑现在软件开发人员使用/ do越来越less,现在几乎所有的阳光下的程序都有一些graphics用户界面。 控制台应用程序更有意义,因为需要不断刷新输出以提供指令或提示用户input新信息。 与GUI相比,向用户提供该信息的文本可以坐在表单上,​​而不需要以编程方式重复。

阅读文件时,我一直使用do-while循环。 我使用了很多包含注释头部的文本文件:

 # some comments # some more comments column1 column2 1.234 5.678 9.012 3.456 ... ... 

我将使用do-while循环读取“column1 column2”行,以便查找感兴趣的列。 这里是伪代码:

 do { line = read_line(); } while ( line[0] == '#'); /* parse line */ 

然后我会做一个while循环来读取文件的其余部分。

作为一个geezer程序员,我的许多学校编程项目使用文本菜单驱动的交互。 几乎所有的主要过程都使用了如下逻辑:

 do display options get choice perform action appropriate to choice while choice is something other than exit 

从上学的时候起,我发现我更频繁地使用while循环。

我看到的应用程序之一是在Oracle中查看结果集。

一旦你有一个结果集,你首先从它(做),从这一点上获取..检查是否提取返回一个元素或没有(find元素..)..同样可能适用于任何其他“类似取指“的实现。

我已经在一个函数中使用它,它返回一个utf-8string中的下一个字符位置:

 char *next_utf8_character(const char *txt) { if (!txt || *txt == '\0') return txt; do { txt++; } while (((signed char) *txt) < 0 && (((unsigned char) *txt) & 0xc0) == 0xc0) return (char *)txt; } 

请注意,这个函数是从头开始编写的,没有经过testing。 关键是你必须做第一步,你必须做,然后才能评估病情。

任何types的控制台input都可以正常运行,因为您第一次提示,并在inputvalidation失败时重新提示。

这在服务器/消费者中是一个相当普遍的结构:

 DOWHILE (no shutdown requested) determine timeout wait for work(timeout) IF (there is work) REPEAT process UNTIL(wait for work(0 timeout) indicates no work) do what is supposed to be done at end of busy period. ENDIF ENDDO 

REPEAT UNTIL(cond)do {...} while(!cond)

有时,等待工作(0)可以更便宜地处理CPU(甚至消除超时计算可能是一个非常高的到来率的改善)。 此外,还有许多排队理论的结果,使繁忙时期的服务人数成为重要的统计数据。 (例如参见Kleinrock – 第1卷)

同理:

 DOWHILE (no shutdown requested) determine timeout wait for work(timeout) IF (there is work) set throttle REPEAT process UNTIL(--throttle<0 **OR** wait for work(0 timeout) indicates no work) ENDIF check for and do other (perhaps polled) work. ENDDO 

在那里check for and do other work可能会过于昂贵的主循环或可能是一个不支持有效的waitany(waitcontrol*,n)types操作的内核,或者可能是一个优先级队列可能会使其他工作饿死的情况,油门被用作饥饿控制。

这种平衡可能看起来像一个黑客,但它可能是必要的。 线程池的盲目使用将完全破坏使用具有专用队列的看pipe程序线程的性能优势,因为使用线程池而不是看护程序线程将需要线程安全实现,因此具有高更新速度的复杂数据结构。

我真的不想进入关于伪代码的争论(例如,是否应该在UNTIL中testingshutdown请求)还是看pipe程序线程与线程池 – 这只是为了给出一个特定的用例控制stream程结构。

这是我个人的意见,但这个问题要求根据经验做出答案:

  • 我已经在C编程了36年,并且我从不使用常规代码中的do / while循环。

  • 这个结构唯一引人注目的用途是在macros中,它可以通过do { multiple statements } while (0)将多个语句包装成单个语句, do { multiple statements } while (0)

  • 我已经看到无数的伪/错误检测或冗余函数调用循环的例子。

  • 我对这个观察结果的解释是程序员倾向于用do / while循环的方式来错误地build模问题。 他们要么错过了一个重要的结局条件,要么就错过了他们走到最后的初始状态的可能失败。

由于这些原因,我相信有一个do / while循环的地方存在一个bug ,我经常挑战新手程序员向我展示一个do / while循环,我不能发现附近的bug。

这种types的循环可以很容易避免:使用for (;;) { ... }并在适当的地方添加必要的终止testing。 需要多于一个这样的testing是相当普遍的。

这是一个典型的例子:

 /* skip the line */ do { c = getc(fp); } while (c != '\n'); 

如果文件没有以换行符结束,这将失败。 这样的文件的一个简单的例子是空文件。

更好的版本是这样的:

 int c; // another classic bug is to define c as char. while ((c = getc(fp)) != EOF && c != '\n') continue; 

或者,这个版本也隐藏了cvariables:

 for (;;) { int c = getc(fp); if (c == EOF || c == '\n') break; } 

尝试searchwhile (c != '\n'); 在任何search引擎,你会发现这样的错误(检索2017年6月24日):

ftp://ftp.dante.de/tex-archive/biblio/tib/src/streams.c中; ,函数getword(stream,p,ignore)有一个do / while并且至less有两个bug:

  • c被定义为char
  • while (c!='\n') c=getc(stream);

结论:避免do / while循环,并在看到错误时寻找错误。

我在研究正确的循环时遇到了这个问题,以适应我的情况。 我相信这将完全满足do while循环比while循环(C#语言更好的实现,因为您声明这是您的主要工作)的常见情况。

我正在根据SQL查询的结果生成一个string列表。 我的查询返回的对象是一个SQLDataReader。 这个对象有一个名为Read()的函数,它将对象推进到下一行数据,如果有另一行,则返回true。 如果没有另一行,它将返回false。

使用这个信息,我想要返回每一行到一个列表,然后停止,当没有更多的数据返回。 一个Do … While循环在这种情况下效果最好,因为它确保在检查是否有另一行之前将项目添加到列表中。 之所以这么做,是因为在检查之前(条件)是在检查时,它也在前进。 在这种情况下使用while循环会导致它绕过第一行,这是由于该特定函数的性质。

简而言之:

这在我的情况下不起作用。

  //This will skip the first row because Read() returns true after advancing. while (_read.NextResult()) { list.Add(_read.GetValue(0).ToString()); } return list; 

这会。

  //This will make sure the currently read row is added before advancing. do { list.Add(_read.GetValue(0).ToString()); } while (_read.NextResult()); return list;