'做'而'与'同时'

时间:2010-07-27 19:09:10

标签: c# c++ c while-loop do-while

  

可能重复:
  While vs. Do While
  When should I use do-while instead of while loops?

我已经编程了一段时间(2年工作+ 4。5年学位+ 1年大学预科课程),我从来没有使用过do-while循环而不是被迫参加编程入门课程。我越来越觉得如果我从未遇到过如此根本的事情,我就会编程错误。

可能是因为我没有遇到正确的情况吗?

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

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

澄清......我知道whiledo-while之间的区别。在检查退出条件然后执行任务时。 do-while执行任务然后检查退出条件。

29 个答案:

答案 0 :(得分:98)

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

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

} while (user says retry);

答案 1 :(得分:59)

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

此外,由于大多数编译器都足够智能来执行此优化,因此反编译代码中的所有循环通常都是do-while(如果反编译器甚至无法从后向本地gotos重构循环)。

答案 2 :(得分:11)

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

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

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

答案 3 :(得分:11)

我在TryDeleteDirectory函数中使用过它。就像这样

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

答案 4 :(得分:10)

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

正如其他人所说,当你想要至少执行一次身体时,你会使用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来表示有效条件。但是,在我匆忙和简要考虑的意见中,如果您使用的语言不允许表示该值的值,则会发生更多这种情况:此变量尚未初始化。

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

答案 5 :(得分:7)

我在学校时用了很多东西,但从那以后就没用了。

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

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

答案 6 :(得分:6)

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

答案 7 :(得分:6)

您总是需要运行一段代码,并且根据其结果,可能需要更多次。同样可以使用常规while循环生成。

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

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

答案 8 :(得分:5)

就这么简单:

前提条件与后置条件

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

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

答案 9 :(得分:4)

我看到这个问题得到了充分的回答,但是想添加这个非常具体的用例场景。你可能会更频繁地开始使用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);

答案 10 :(得分:2)

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

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

答案 11 :(得分:2)

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

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

答案 12 :(得分:1)

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

答案 13 :(得分:1)

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

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

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

  • 将多个语句包装到单个语句中
  • 我看过无数个do / while循环的例子,其中包含伪造错误检测或冗余函数调用。

  • 我对此观察的解释是程序员在根据do / while循环进行思考时往往会错误地对问题进行建模。他们要么错过一个重要的结局条件,要么他们错过了他们最终可能失败的初始条件。

由于这些原因,我开始相信哪里有do / while循环,有一个错误,我经常挑战新手程序员来展示我在do / while循环中找不到附近的错误。

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

这是一个典型的例子:

/* 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中,函数getword(stream,p,ignore)有一个do / while,确定至少有2个错误:

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

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

答案 14 :(得分:1)

我无法想象你如何在不使用do...while循环的情况下走了这么久。

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

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

答案 15 :(得分:1)

这是我的理论为什么大多数人(包括我)喜欢while(){}循环来做{} while():while(){}循环可以很容易地适应像do..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)
}

现在我们在两端有两个检查,如果不变的更改,你必须在两个地方更新它。在某种程度上,...就像工具箱中你从不使用的专用螺丝刀一样,因为标准螺丝刀可以满足您的一切需求。

答案 16 :(得分: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);
}

情况2: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的生成。因此可以归结为大型项目和优化。

答案 17 :(得分:0)

我喜欢将这两个理解为:
while->“重复直到”,
do ... while->“如果重复”。

答案 18 :(得分:0)

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

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

其中check for and do other work放入主循环可能过于昂贵,或者可能是不支持高效waitany(waitcontrol*,n)类型操作的内核,或者可能是优先级排队可能会使其他工作匮乏的情况油门用作饥饿控制。

这种平衡看起来像是一种黑客攻击,但它可能是必要的。盲目使用线程池将完全破坏使用具有私有队列的看管线程对高更新率复杂数据结构的性能优势,因为使用线程池而不是看管线程需要线程安全实现。 / p>

我真的不想进入关于伪代码的争论(例如,是否应该在UNTIL中测试关闭请求)还是看管线程与线程池之间的争论 - 这只是为了给出特定的风格控制流结构的用例。

答案 19 :(得分:0)

任何类型的控制台输入都可以与do-while配合使用,因为您第一次提示,并在输入验证失败时重新提示。

答案 20 :(得分: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;
}

请注意,此功能是从头脑中编写的,未经过测试。关键是你必须要做第一步,你必须先做,然后才能评估病情。

答案 21 :(得分:0)

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

一旦你有了一个结果集,你首先从它获取(do)并从那一点开始..检查fetch是否返回一个元素(当找到元素时)..同样可能适用于任何其他“类似fetch”的实现。

答案 22 :(得分:0)

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

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

从上学日开始,我发现我更频繁地使用while循环。

答案 23 :(得分: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循环来读取文件的其余部分。

答案 24 :(得分: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,向用户提供该信息的文本可以只放在表单上,​​而不需要以编程方式重复。

答案 25 :(得分:0)

当我在文件的开头读取一个标记值时,我使用了do while,但除此之外,我不认为这种结构不太常用是不正常的 - - 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)

答案 26 :(得分:0)

while循环在循环之前检查条件,do...while循环检查循环后的条件。这很有用,你想根据循环运行的副作用,或者像其他海报所说,如果你想让循环运行至少一次。

我知道你来自哪里,但do-while是最少使用的东西,而我从未使用过自己。你做错了。

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

答案 27 :(得分:-1)

我在研究适当的循环时遇到了这种情况,以便用于我所拥有的情况。我相信这将完全满足一个常见的情况,即while。while循环是一个比while循环更好的实现(C#语言,因为你声明这是你的主要工作)。

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

使用此信息,我想将每行返回到列表,然后在没有更多数据返回时停止。 A Do ... While循环在这种情况下效果最好,因为它确保在检查是否存在另一行之前将向列表添加项目。在检查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;

答案 28 :(得分:-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;