【问题标题】:Infinite Loops and Early Return Statements无限循环和提前返回语句
【发布时间】:2011-05-28 07:15:17
【问题描述】:

我有一个简单的控制台应用程序,它输出一个菜单并等待用户输入。执行适当的操作后,整个过程重复。输入特定字符串时程序退出。这是通过无限循环和提前返回语句实现的:

int main()
{
    while (true)
    {
        OutputMenu();

        string UserChoice;
        cin >> UserChoice;

        // ...

        if (UserChoice == "exit") return 0;
    }
}

根据我的老师的说法,使用无限循环并使用 return 语句破解我的方法是不好的做法。他建议如下:

int main()
{
    bool ShouldExit = false;
    while (!ShouldExit)
    {
        OutputMenu();

        string UserChoice;
        cin >> UserChoice;

        // ...

        if (UserChoice == "exit") ShouldExit = true;
    }

    return 0;
}
  • 使用无限循环和提前返回语句真的是个坏主意吗?
  • 如果是这样,是有技术原因还是只是不好的做法?

【问题讨论】:

  • 请注意,在第二种情况下,您忘记了 return 一个值。
  • @ereOn:你不需要从main返回值。
  • @Billy ONeal:当然。有很多事情不需要去做。但我相信这样做看起来更一致。
  • @ereOn:虽然我同意在允许您这样做的地方省略return 声明不会给您带来任何好处,并且总是自己编写,但我不会向其他人指出这一点如果他们忘记了重要的事情。
  • @sbi 在这种情况下,我认为有助于区分两者是相关的。

标签: c++ infinite-loop


【解决方案1】:

从技术上讲,只要没有您通过使用 return 跳过的代码,它就没有太多内容。

不过,你老师的建议更易读,哪怕只是因为“ShouldExit”的明显含义。

【讨论】:

  • 更不用说在实际情况下,在退出程序之前通常还有一些工作要做。使用 RAII 可以解决这个问题,但仍然:它确实更具可读性。
  • @ereOn - 完全同意,当我说“只要没有您通过使用返回跳过的代码”时,这就是我想要表达的意思。
【解决方案2】:

使用受控变量,您将能够在退出函数之前处理退出条件(while 之后的代码)。

【讨论】:

    【解决方案3】:

    这两种方法的唯一区别是,在第二种方法中,您在退出while 循环后仍然可以执行某些操作,而在第一种方法中,您是从函数本身返回的;在while 之后你什么也做不了。

    但是,我建议使用这个简单的代码:除了维护变量之外,您还可以像这样使用break:

    while (true)
    {
           //your code
    
           if (UserChoice == "exit") 
                break;
    
           //your code
    }
    

    变量ShouldExit 不再需要了!

    【讨论】:

      【解决方案4】:

      取决于语言。如果您使用 C 语言编写,那么“一个入口,一个出口”的理念是有道理的——您想要一个清理函数使用的资源的地方,这样您以后就不会忘记。如果您使用 C++,那么无论如何您都应该使用 RAII 进行清理,在这种情况下,我完全不同意您的老师的观点。根据需要使用returns 以使代码尽可能清晰。

      (虽然我会在 C++ 中使用 for (;;) 而不是 while (true) 来生成无限循环)

      【讨论】:

        【解决方案5】:

        这可能是适合do...while 的少数情况之一。我避免添加额外的布尔状态变量,除非它们确实使代码更清晰。

        int main()
        {
            string UserChoice;
            do
            {
                OutputMenu();
        
                cin >> UserChoice;
        
                // ...
        
            } while (UserChoice != "exit");
        }
        

        但是,对于用户输入循环,我通常会创建一个函数来返回输入是否成功。就目前而言,如果cin 关闭,代码很容易以无限循环结束。

        例如

        bool GetNonExitInput( std::istream& in, std::string& s )
        {
            OutputMenu();
            in >> s;
        
            return in.good() && s != "exit";
        }
        
        
        int main()
        {
            std::string UserChoice;
        
            while (GetNonExitInput(std::cin, UserChoice))
            {
                // ...
            }
        }
        

        【讨论】:

        • +1 次小修正,将“UserChoice”的声明移到 do{}while 循环开始之前。
        • @MeThinks:这不是次要(!)更正,这是一个必不可少的步骤,因为它用于 while 条件。否则,我更喜欢尽可能限制变量的范围,因此在需要之前立即声明它是我的偏好。 @sbi:感谢您的编辑。
        【解决方案6】:

        我认为你老师的意思是退出条件很容易保持。这是因为 while 循环之后的代码清理。如果您将执行硬返回,则不会执行 while 循环之后的所有内容。这可以通过使用 break 而不是 return 来防止。

        int main()
        {
         //create a file
        
         while (true)
         {
          OutputMenu();
        
          string UserChoice;
          cin >> UserChoice;
        
          //write UserChoice to file
        
          // ...
        
          if (UserChoice == "exit") return 0;
         }
        
         //close file 
        }
        

        //close file 将不会被执行!

        【讨论】:

        • 应始终使用 RAII 容器关闭文件。由于异常,函数中到处都有隐式返回点。
        【解决方案7】:

        两者都可以,但你需要按照教授的要求去做。你会发现在工业领域也是如此。一些公司可能有一个编码标准,规定花括号在新行上,而另一些公司则希望它们从开始块的行开始。没有真正的理由偏爱其中一个,因此最好按照领导者的意愿行事。

        【讨论】:

          【解决方案8】:

          break 语句专门用于退出循环。

          【讨论】:

            【解决方案9】:

            我通常更喜欢你老师的建议,因为它更容易阅读和理解停止循环的条件。
            如果你有一个带有 return 语句的无限循环,那么对于没有编写代码的人来说,通过代码并弄清楚程序何时会遇到 return 语句会有点困难。

            另外,我通常不喜欢提前返回,因为维护代码的人很容易引入错误,例如:

            int main()
            {
                // code added by some other programmer:
                importantInitialization();
            
                while (true)
                {
                    OutputMenu();
            
                    // code added by some other programmer:
                    Something *st = new Something();
            
                    string UserChoice;
                    cin >> UserChoice;
            
                    // ...
            
                    if (UserChoice == "a") runA();
                    else if (UserChoice == "b") runB();
                    else if (UserChoice == "c") runC();
                    else if (UserChoice == "d") runD();
                    else if (UserChoice == "exit") return 0;
                    else if (UserChoice == "help") showHelp();
            
                    // code added by some other programmer:
                    delete st; // this would not run on the last loop
                }
            
                // code added by some other programmer:
                importantCleanUp(); // this would never run 
            }
            

            当然,在这种特殊情况下很容易发现问题,但是在维护更复杂的函数时,您可以看到早期的 return 语句可能会使其更容易出现这种缺乏注意力的错误。

            【讨论】:

            • 如果程序员看到一个无限循环,甚至懒得模糊地扫描循环以了解循环是如何退出的,那是他们是白痴的错,而不是你。
            【解决方案10】:

            在我看来,这两种方式都很好,但第二种方式“更漂亮”。

            在编程中,以您能想到的最简单的方式编写代码很重要,如果您将被替换或出于任何其他原因,其他程序员也可以轻松理解您的代码。

            您的两个代码不涉及复杂性问题,因此正如我所说,它们都很好,但我不喜欢第一个代码的想法是使用“return”语句而没有任何真正需要“return”在这里声明。

            还有另一种编写此代码的方式,比你的方式更好(在我看来),但不如你老师的方式。

            int main()
            {
                bool ShouldExit = false;
                while ( true )
                {
                    OutputMenu();
            
                    string UserChoice;
                    cin >> UserChoice;
            
                    // ...
            
                    if (UserChoice == "exit") break;
                }
            }
            

            我不喜欢你的第一个代码和我上面的代码的另一个主要原因是因为使用了无限循环,当你让自己习惯无限循环时,让你变得更复杂只是时间问题存在重大错误的程序。

            再说一遍 - 我写的所有东西都只是我个人的观点,而不是福音真理。

            罗特姆

            【讨论】:

              【解决方案11】:

              我在哲学上反对while(true)。它的意思是“永远循环”,你永远不想永远循环。

              另一方面,我在哲学上也反对仅记录可以通过其他方式找到的状态的布尔变量。等待中有一个错误,它可能并不总是与它应该反映的状态正确同步。在这种情况下,我更喜欢这样的代码:

              int main()
              {
                  string UserChoice = "not started"; // or empty string
                  while (UserChoice != "exit")
                  {
                      OutputMenu();
              
                      string UserChoice;
                      cin >> UserChoice;
              
                      // ...
              
                  }
              
                  return 0;
              }
              

              【讨论】:

              • while(true) 是“循环直到中断”的惯用语,尽管在 VB 中“Do/Loop”语句将是更常见的习语(尤其是因为旧方言不允许中断同时,它们也不允许在“Do/Loop”语句上指定退出条件(除了“Exit Do”所暗示的那些条件。在 C 中,与 VB 使用并行,我的循环直到中断的常规习惯用法是 do/而(1)。
              • @supercat:实际上,for (;;) 是在 C 和 C++ 中实现这一目标的理想方式。 (也就是说,因为true 不是 C 中的关键字,而 C++ 只是复制了它)
              • @Billy:我从未使用过 for(;;)。对我来说,它“看起来”坏了。我使用 do{}while(1) 编写一个将一直运行到中断的循环,使用 while(1){} 编写一个永远不会退出底部的循环(通常在嵌入式系统中;循环可能会退出通过 setjmp、异常等)
              • @supercat:只是说多年来 C 语言的理念。我没有制定规则:)
              【解决方案12】:

              我认为带有 break 的 while(true) 是最好的,原因有几个。引入一个变量来存储退出条件很容易出错,更多的变量意味着更多的可能出错。此外,break 语句专门用于跳出循环。最后,与 for(;;) 不同,while(true) 干净、易读、简洁,而 for(;;) 无缘无故地试图变得聪明。

              补充一点,为了提高可读性和理解力,将退出条件尽可能靠近循环顶部:

              while (true) {
              
                  OutputMenu();
              
                  string UserChoice;
                  cin >> UserChoice;
              
                  if (UserChoice == "exit")
                      break;
              
                  // process other options here
              }
              

              【讨论】:

                【解决方案13】:

                使用本地标志的例程,就状态分析而言,相当于将相同的代码复制了两次,其中一个副本对应于该标志为真,另一个副本相当于该标志为假,以及任何改变它们之间跳跃状态的代码。如果有 n 个标志,则相当于 2^n 个代码副本(尽管如果某些标志是互斥的,则其中一些可能无法访问且不相关)。

                虽然有时标志是最实用的处理方式,但它们会增加代码的复杂性。当确实需要复杂性时,标志可能是提供它的最干净的方式。当有一种干净实用的方法来编写避免标志的代码时,应该这样做。当然,有时可能不清楚是使用标志还是避免使用标志更好(例如

                flag = condition_which_must_be_tested_here(); action_which_will_disturb_the_condition(); 如果(标志) 做一点事(); 别的 do_something_else();

                对

                if (condition_which_must_be_tested_here()) { action_which_will_disturb_the_condition(); 做一点事(); } 别的 { action_which_will_disturb_the_condition(); do_something_else(); }

                但在没有标志且无需复制任何代码的情况下,通常更可取的是这样的版本。

                【讨论】:

                  猜你喜欢
                  • 2015-05-05
                  • 2017-10-13
                  • 1970-01-01
                  • 1970-01-01
                  • 2016-09-27
                  • 2013-03-26
                  • 2017-05-15
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多