104

可能的重复:
While vs. Do While
什么时候应该使用 do-while 而不是 while 循环?

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

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

有哪些示例需要使用 do-while 而不是 while?

(我的学习几乎都是 C/C++,而我的工作是 C#,所以如果有另一种语言绝对有意义,因为 do-while 的工作方式不同,那么这些问题并不适用。)

while澄清一下......我知道 a和 a之间的区别do-while。While 检查退出条件,然后执行任务。do-while执行任务,然后检查退出条件。

4

31 回答 31

133

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

do
{
   try to access resource...
   put up message box with retry option

} while (user says retry);
于 2010-07-27T19:10:36.027 回答
77

如果编译器不擅长优化,do-while 会更好。do-while 只有一个条件跳转,而 for 和 while 则有条件跳转和无条件跳转。对于流水线且不进行分支预测的 CPU,这会对紧密循环的性能产生很大影响。

此外,由于大多数编译器都足够聪明地执行此优化,因此在反编译代码中发现的所有循环通常都是 do-while(如果反编译器甚至费心从向后的本地 goto 重构循环)。

于 2010-07-27T19:15:39.200 回答
13

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

do
{
    try
    {
        DisableReadOnly(directory);
        directory.Delete(true);
    }
    catch (Exception)
    {
        retryDeleteDirectoryCount++;
    }
} while (Directory.Exists(fullPath) && retryDeleteDirectoryCount < 4);
于 2010-07-27T19:24:46.910 回答
11

当您想至少执行一次某事时,Do while 很有用。至于使用 do while 与 while 的一个很好的例子,假设你想做以下事情:一个计算器。

您可以通过使用循环并在每次计算后检查该人是否想退出程序来解决此问题。现在您可能可以假设一旦程序打开,该人至少想要执行此操作一次,因此您可以执行以下操作:

do
{
    //do calculator logic here
    //prompt user for continue here
} while(cont==true);//cont is short for continue
于 2010-07-27T19:17:35.293 回答
11

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

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

好吧,我能想到的最明显的一类情况是检查条件的初始(“未准备好”)值与您想退出时的值相同。这意味着您需要执行一次循环体以将条件设​​置为非退出值,然后根据该条件执行实际重复。由于程序员如此懒惰,有人决定将其包装在控制结构中。

因此,例如,从一个超时的串行端口读取字符可能采用以下形式(在 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

为循环创建新范围的语言的额外好处是: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)

所以这是我观点的关键:在具有可空类型的语言中,这种情况initial_value == exit_value出现的频率要低得多,可能就是你没有遇到它的原因。我并不是说它永远不会发生,因为有时函数会返回None以表示有效条件。但在我匆忙和简单考虑的意见中,如果您使用的语言不允许使用表示的值:这个变量还没有被初始化,这种情况会发生得更多。

这不是完美的推理:实际上,既然空值很常见,它们只是构成变量可以采用的一组有效值中的另一个元素。但实际上,程序员有办法区分处于可感知状态(可能包括循环退出状态)的变量和处于未初始化状态的变量。

于 2010-07-28T05:16:56.453 回答
7

我在学校的时候用过它们,但从那以后就没有那么多了。

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

事实上,如果我对循环退出条件有点犹豫,有时我会发现在需要的地方使用 exit 语句开始编写循环很有用for (;;) {},然后当我完成后,我可以看看它是否可以“通过在for括号内移动初始化、退出条件和/或增量代码来清理” 。

于 2010-07-27T19:16:46.250 回答
6

do while是如果您想至少运行一次代码块。while另一方面,并​​不总是根据指定的标准运行。

于 2010-07-27T19:11:10.753 回答
6

在这种情况下,您总是需要运行一段代码一次,并且根据其结果,可能需要运行更多次。while使用常规循环也可以产生相同的效果。

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

do
{
    rc = get_something();
}
while (rc == wrong_stuff);
于 2010-07-27T19:30:53.600 回答
5

就这么简单:

前置条件与后置条件

  • while (cond) {...} - 前提条件,它仅在检查后执行代码。
  • do {...} while (cond) - 后置条件,代码至少执行一次。

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

于 2010-07-27T19:34:08.773 回答
4

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

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);
于 2010-07-28T00:06:32.470 回答
2

到目前为止的答案总结了 do-while 的一般用途。但是OP要求一个例子,所以这里是一个:获取用户输入。但是用户的输入可能是无效的——所以你要求输入,验证它,如果它有效则继续,否则重复。

使用do-while,您可以在输入无效时获得输入。使用常规的 while 循环,您会获得一次输入,但如果它无效,您会一次又一次地获得它,直到它有效为止。如果循环体变得更复杂,不难看出前者更短、更优雅、更易于维护。

于 2010-07-27T19:19:05.660 回答
2

我已经将它用于多次读取相同结构的阅读器。

using(IDataReader reader = connection.ExecuteReader())
{
    do
    {
        while(reader.Read())
        {
            //Read record
        }
    } while(reader.NextResult());
}
于 2010-07-27T19:34:40.940 回答
2

我喜欢将这两个理解为:
while-> 'repeat until',
do ... while-> 'repeat if'。

于 2020-10-24T15:23:06.790 回答
1

do while在读取文件开头的标记值时使用了 a ,但除此之外,我不认为这种结构不太常用是不正常的 - do-whiles 真的是情景。

-- 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)
于 2010-07-27T19:20:14.887 回答
1

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

于 2010-07-27T19:24:34.333 回答
1

以下是我的理论,为什么大多数人(包括我)更喜欢使用 while(){} 循环来执行{}while():while(){} 循环可以很容易地调整为像 do..while() 循环一样执行,而相反不是真的。while 循环以某种方式“更通用”。程序员也喜欢易于掌握的模式。while 循环一开始就说明它的不变量是什么,这是一件好事。

这就是我所说的“更一般”的东西。采取这个 do..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 就像工具箱中的专用螺丝刀,您从不使用,因为标准螺丝刀可以满足您的一切需求。

于 2010-07-27T19:57:37.087 回答
1

我无法想象如果不使用do...while循环,您是如何做到这么长时间的。

现在另一台显示器上有一个,并且该程序中有多个这样的循环。它们都是以下形式:

do
{
    GetProspectiveResult();
}
while (!ProspectIsGood());
于 2010-07-28T00:14:04.557 回答
1

这是服务器/消费者中非常常见的结构:

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 - Vol 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

wherecheck for and do other work放入主循环可能非常昂贵,或者可能是不支持有效waitany(waitcontrol*,n)类型操作的内核,或者可能是优先队列可能使其他工作饿死而节流阀用作饥饿控制的情况。

这种类型的平衡看起来像是一种技巧,但它可能是必要的。盲目使用线程池将完全破坏使用具有私有队列的看守线程来获得高更新率复杂数据结构的性能优势,因为使用线程池而不是看守线程将需要线程安全实现。

我真的不想就伪代码(例如,是否应在 UNTIL 中测试关闭请求)或看守线程与线程池进行辩论——这只是为了给出一个特定用例的味道控制流结构。

于 2010-07-28T04:49:28.367 回答
1

这是我个人的看法,但这个问题需要一个根植于经验的答案:

  • 我已经用 C 语言编程了 38 年,而且我从不在常规代码中使用do/while循环。

  • 这种结构唯一引人注目的用途是在宏中,它可以通过do { multiple statements } while (0)

  • 我见过无数带有虚假错误检测或冗余函数调用的do/循环示例。while

  • 我对这一观察的解释是,程序员在考虑do/while循环时往往会错误地建模问题。他们要么错过了一个重要的结束条件,要么错过了他们移动到最后的初始条件的可能失败。

由于这些原因,我开始相信do/while循环的地方就有 bug,我经常挑战新手程序员,让我展示一个do/while循环,但我无法在附近发现 bug。

这种类型的循环很容易避免:使用 afor (;;) { ... }并在适当的地方添加必要的终止测试。需要不止一种这样的测试是很常见的。

这是一个经典的例子:

/* 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;

或者,这个版本也隐藏了c变量:

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

尝试while (c != '\n');在任何搜索引擎中搜索,您会发现诸如此类的错误(检索于 2017 年 6 月 24 日):

ftp://ftp.dante.de/tex-archive/biblio/tib/src/streams.c, function getword(stream,p,ignore),有一个do/while确实至少有2个错误:

  • c被定义为一个char
  • 有一个潜在的无限循环while (c!='\n') c=getc(stream);

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

于 2017-06-24T17:12:58.810 回答
0

while循环检查循环前的条件,do...while循环检查循环后的条件。如果您希望将条件基于循环运行的副作用,或者像其他海报所说的那样,如果您希望循环至少运行一次,这很有用。

我知道您来自哪里,但这do-while是大多数人很少使用的东西,而且我自己也从未使用过。你没有做错。

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

于 2010-07-27T19:14:13.307 回答
0

我在使用do/while循环时遇到的最常见场景是在一个小控制台程序中,该程序基于一些输入运行,并且会根据用户的喜好重复多次。显然,控制台程序不运行一次是没有意义的。但除了第一次之外,这取决于用户 - 因此do/while而不仅仅是while.

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

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

我怀疑这些天软件开发人员使用do/while越来越少,现在几乎每个程序都具有某种 GUI。控制台应用程序更有意义,因为需要不断刷新输出以提供说明或提示用户新信息。相比之下,使用 GUI,向用户提供该信息的文本可以直接放在表单上,​​而无需以编程方式重复。

于 2010-07-27T19:31:15.143 回答
0

在读取文件时,我一直使用 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循环来阅读文件的其余部分。

于 2010-07-27T19:46:55.847 回答
0

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

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

从学生时代开始,我发现我更频繁地使用 while 循环。

于 2010-07-27T19:54:22.627 回答
0

当我们查看结果集时,我看到的其中一个应用程序是在 Oracle 中。

一旦你有一个结果集,你首先从中获取(做),然后从那一刻开始......检查获取是否返回一个元素(当找到元素时......)......这同样可能适用于任何其他“ fetch-like”的实现。

于 2010-07-27T19:56:39.020 回答
0

我在一个函数中使用了它,该函数返回 utf-8 字符串中的下一个字符位置:

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;
}

请注意,此功能是根据头脑编写的,未经测试。关键是无论如何你都必须做第一步,并且在评估条件之前你必须这样做。

于 2010-07-27T20:14:27.773 回答
0

任何类型的控制台输入都适用于 do-while,因为您是第一次提示,并在输入验证失败时重新提示。

于 2010-07-27T20:23:25.123 回答
0

尽管这里有很多答案是我的看法。这一切都归结为优化。我将展示两个示例,其中一个比另一个更快。

情况1:while

string fileName = string.Empty, fullPath = string.Empty;

while (string.IsNullOrEmpty(fileName) || File.Exists(fullPath))
{
    fileName = Guid.NewGuid().ToString() + fileExtension;
    fullPath = Path.Combine(uploadDirectory, fileName);
}

案例二:do while

string fileName = string.Empty, fullPath = string.Empty;

do
{
    fileName = Guid.NewGuid().ToString() + fileExtension;
    fullPath = Path.Combine(uploadDirectory, fileName);
}
while (File.Exists(fullPath));

所以有两个会做完全相同的事情。但是有一个根本的区别,那就是 while 需要一个额外的语句才能进入 while。这是丑陋的,因为假设Guid该类的所有可能场景都已经被采用,除了一个变体。这意味着我将不得不循环5,316,911,983,139,663,491,615,228,241,121,400,000。每次我到我的 while 语句结束时,我都需要进行string.IsNullOrEmpty(fileName)检查。所以这会占用一点点 CPU 工作的一小部分。但是,这个非常小的任务是否将Guid班级可能的组合乘以我们正在谈论的小时、天、月或额外时间?

当然这是一个极端的例子,因为你可能不会在生产中看到它。但如果我们考虑 YouTube 算法,他们很可能会遇到一些 ID 已经被占用的 ID 生成。所以它归结为大项目和优化。

于 2020-10-20T11:24:47.993 回答
0

即使在教育参考资料中,您也几乎找不到做...虽然的例子。直到最近,在阅读了 Ethan Brown 漂亮的书《Learning JavaScript 》之后,我遇到了一个 do...while 定义良好的示例。话虽如此,我相信如果您在日常工作中找不到这种结构的应用程序是可以的。

于 2021-12-16T17:15:55.533 回答
0

确实do/while循环非常罕见。我认为这是因为很多循环的形式

while(something needs doing)
    do it;

一般来说,这是一个很好的模式,并且它具有通常需要的特性,即如果不需要做任何事情,循环就会运行零次。

但是偶尔,无论如何,您肯定要至少进行一次循环旅行,这是有充分理由的。我最喜欢的例子是:将整数转换为其十进制表示为字符串,即实现printf("%d"),或半标准itoa()函数。

为了说明,这里是一个相当简单的实现itoa()。这不是传统”的表述。如果有人好奇,我将在下面更详细地解释它。但关键是它体现了规范算法,重复除以10以从右边挑选数字,并且使用普通while循环编写......这意味着它有一个错误。

#include <stddef.h>

char *itoa(unsigned int n, char buf[], int bufsize)
{
    if(bufsize < 2) return NULL;
    char *p = &buf[bufsize];
    *--p = '\0';

    while(n > 0) {
        if(p == buf) return NULL;
        *--p = n % 10 + '0';
        n /= 10;
    }

    return p;
}

如果你没有发现它,错误是如果你要求它转换整数,这段代码什么也不返回——一个空字符串0。所以这是一个例子,当“无”可做时,我们希望代码什么都不做——我们总是希望它至少产生一个数字。所以我们总是希望它至少完成一次循环。所以do/while循环只是门票:

    do {
        if(p == buf) return NULL;
        *--p = n % 10 + '0';
        n /= 10;
    } while(n > 0);

所以现在我们有一个循环,它通常在到达 0时停止n,但如果n最初是 0——如果你传入一个 0——它会"0"根据需要返回字符串。

正如所承诺的,这里有更多关于itoa这个例子中的函数的信息。你传递给它的参数是: anint来转换(实际上是 an unsigned int,这样我们就不必担心负数);要渲染到的缓冲区;以及该缓冲区的大小。它返回一个char *指向缓冲区的指针,指向渲染字符串的开头。(或者NULL如果它发现你给它的缓冲区不够大,它就会返回。)这个实现的“非传统”方面是它从右到左填充数组,这意味着它不必反转最后的字符串——也意味着它返回给你的指针通常不是到缓冲区的开头。所以你必须使用它返回给你的指针作为要使用的字符串;你不能调用它,然后假设你交给它的缓冲区是你可以使用的字符串。

最后,为了完整起见,这里有一个小测试程序来测试这个版本itoa

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    int n;
    if(argc > 1)
        n = atoi(argv[1]);
    else {
        printf("enter a number: "); fflush(stdout);
        if(scanf("%d", &n) != 1) return EXIT_FAILURE;
    }

    if(n < 0) {
        fprintf(stderr, "sorry, can't do negative numbers yet\n");
        return EXIT_FAILURE;
    }

    char buf[20];
    printf("converted: %s\n", itoa(n, buf, sizeof(buf)));

    return EXIT_SUCCESS;
}
于 2021-12-20T11:10:42.990 回答
-1

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

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

使用此信息,我想将每一行返回到一个列表,然后在没有更多数据返回时停止。Do... While 循环在这种情况下效果最好,因为它确保在检查是否有另一行之前将项目添加到列表中。这必须在检查 while(condition) 之前完成的原因是,当它检查时,它也会前进。由于特定函数的性质,在这种情况下使用 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;
于 2017-01-10T17:42:46.503 回答
-1
Console.WriteLine("hoeveel keer moet je de zin schrijven?");
        int aantal = Convert.ToInt32(Console.ReadLine());
        int counter = 0;

        while ( counter <= aantal)
        {
            Console.WriteLine("Ik mag geen stiften gooien");
            counter = counter + 1;
于 2017-12-14T21:36:09.857 回答