【问题标题】:Goto and Code Repetition - Are they avoidable in this case?Goto 和 Code Repetition - 在这种情况下可以避免吗?
【发布时间】:2023-03-24 12:15:01
【问题描述】:

我最近遇到了一个编程问题,在我看来解决它的最优化方法是使用 goto,尽管这不是一个好习惯。问题是:告诉用户输入一个正自然数(> 0)并读取输入。如果这个数字是有效的,告诉用户那个数字的平方。在输入正确的情况下执行此操作。我想出了一些解决方案,但似乎都有问题。以下是其中两个:
解决方案 1 - 问题:使用 goto

#include <stdio.h>

int main()
{
    int num;

_LOOP:
    printf("Enter a positive natural number: ");
    scanf("%i", &num);

    if (num > 0) {
        printf("Square: %i\n", num * num);
        goto _LOOP;
    }

    printf("Invalid number\n");

    return 0;
}

解决方案 2 - 问题:仔细检查 num > 0(代码重复)

#include <stdio.h>

int main()
{
    int num;

    do {
        printf("Enter a positive natural number: ");
        scanf("%i", &num);

        if (num > 0)
            printf("Square: %i\n", num * num);
    } while (num > 0);
    
    printf("Invalid number\n");

    return 0;
}

显然,有更多的方法可以解决这个问题,但我想出的所有其他方法都没有使用 goto 遇到相同的代码重复问题。那么,是否有一种解决方案可以避免 goto 和代码重复?如果没有,我应该去哪一个?

【问题讨论】:

  • 您可能对continuebreak 语句感兴趣。
  • 我不愿意发布一个实际的答案,但是......在scanf 调用之后带有if (num &lt;= 0) break; 的“无限”while(1) { ... } 循环可以解决问题。这是 C 和 C++ 中相当常见的“范式”。
  • 仍然有一个条件循环 - while(true) - 检查其中的另一个条件 - if (num
  • @rdbo 没有条件。编译器会将其替换为无条件分支指令。
  • @EugeneSh。哦,我明白你在说什么。因为循环总是分支,编译器会确保程序甚至不会检查条件,因此达到与 goto 相同的结果并同时避免它

标签: c loops goto


【解决方案1】:

这是一半的答案;尝试填写缺少的内容。请记住,有时将循环构造为“做某事直到...”而不是“做某事...”

    for (;;) {
        printf("Enter a positive natural number: ");
        scanf("%i", &num);
        if (num <= 0)
            break;
        printf("Square: %i\n", num * num);
    }
    printf("Invalid number\n");

[用@rdbo 的回答更新]

【讨论】:

  • 添加 if (num &lt;= 0) break; 这样我就可以将其标记为解决方案(编辑队列已满)。我知道,因为循环总是分支,它对执行流程的影响与我的解决方案中使用的 goto 相同,并且仍然避免它。
【解决方案2】:

跳出循环怎么样?这基本上是一个 goto 语句到循环结束,没有显式使用 goto

#include <stdio.h>

int main()
{
    int num;

    while(1) {
        printf("Enter a positive natural number: ");
        scanf("%i", &num);

        if (num > 0) {
            printf("Square: %i\n", num * num);
        } else {
            printf("Invalid number\n");
            break;
        }
    }

    return 0;
}

【讨论】:

  • 实际上,break; 就像一个goto语句紧跟封闭循环continue 语句更像是“转到循环的 end”。
【解决方案3】:

另一个选项:如果满足继续条件,则检查存储的布尔值。它比无限循环/中断方法(对我而言)更容易阅读,并且没有代码重复。

#include <stdio.h>
#include <stdbool.h>

int main()
{
    int num;
    bool bContinue;

    do {
        printf("Enter a positive natural number: ");
        scanf("%i", &num);

        if (num > 0){
            printf("Square: %i\n", num * num);
            bContinue = true;
        }
        else{
            printf("Invalid number\n");
            bContinue = false;
        }
    } while (bContinue);             

    return 0;
}

【讨论】:

    【解决方案4】:

    对于初学者,如果您期望一个非负数,那么变量 num 应该是无符号整数类型,例如 unsigned int

    正如您在问题中所写的那样,用户可以输入无效数据或中断输入。你必须处理这种情况。

    同样,乘法 num * num 会导致溢出。

    而使用goto 而不是循环确实是个坏主意。

    请注意,您应该在使用它们的最小范围内声明变量。

    对这样的任务使用 for 循环也是一个坏主意。使用 while 循环很有表现力。

    程序可以如下所示

    #include <stdio.h>
    #include <stdbool.h>
    
    int main(void) 
    {
        while ( true )
        {
            printf( "Enter a positive natural number: " );
            
            unsigned int num;
    
            if ( scanf( "%u", &num ) != 1 || num == 0 ) break;
    
            printf( "Square: %llu\n", ( unsigned long long )num * num );
        }
    
        puts( "Invalid number" );
    
        return 0;
    }
    

    程序输出可能看起来像

    Enter a positive natural number: 100000000
    Square: 10000000000000000
    Enter a positive natural number: 0
    Invalid number
    

    或者将最后一个输出语句移到while语句中会更好。例如

    #include <stdio.h>
    #include <stdbool.h>
    
    int main(void) 
    {
        while ( true )
        {
            printf( "Enter a positive natural number: " );
            
            unsigned int num;
    
            if ( scanf( "%u", &num ) != 1 || num == 0 )
            {
                puts( "Invalid number" );
                break;
            }
            
            printf( "Square: %llu\n", ( unsigned long long )num * num );
        }
    
        return 0;
    }
    

    【讨论】:

      【解决方案5】:

      我有点惊讶没有人建议功能分解。 与其编写一大堆原始语句,不如将main 分割成更小的函数。 除了可读性/可维护性的好处外,它还有助于以非常自然的方式消除代码重复。

      在 OP 的情况下,从最终用户那里获取输入是一项单独的职责,并且是单独功能的不错选择。

      static bool user_enters_number(int *ptr_to_num)
      {
          printf("Enter a positive natural number: ");
          return scanf("%i", ptr_to_num) == 1;
      }
      

      注意user_enters_number 显式测试scanf 的返回值。 这改进了文件结束处理。

      同样,您可以赋予数字验证自己的功能。 这可能看起来有点矫枉过正(只是num &gt; 0,对吗?),但它让我们有机会将验证与生成的错误消息结合起来。 在main 的末尾打印“无效号码”感觉不对。无效数字不是唯一的退出条件;文件结尾是另一个。 因此,我将让验证函数确定消息。 作为奖励,这使得支持多种错误类型成为可能(例如,负数和零的单独消息)。

      static bool is_valid_number(int num)
      {
          bool ok = (num > 0);
          if (!ok) printf("Invalid number\n");
          return ok;
      }
      

      我们现在有两个布尔类型的函数,它们可以与&amp;&amp; 巧妙地链接在一起并放入循环的条件部分,这是一种惯用的说法:如果这些函数中的任何一个失败(即返回 false ),立即退出循环。

      剩下的是一个非常干净的main函数。

      int main(void)
      {
          int num;
          while (user_enters_number(&num) && is_valid_number(num))
          {
              printf("Square: %i\n", num * num);
          }
      }
      

      要了解可维护性方面的好处,请尝试重写此代码,使其接受两个数字并打印其乘积。

      int main(void)
      {
          int num1, num2;
          while (user_enters_number(&num1) && is_valid_number(num1) &&
                 user_enters_number(&num2) && is_valid_number(num2))
          {
              printf("Product: %i\n", num1 * num2);
          }
      }
      

      这些变化是微不足道的,并且仅限于一个函数 (尽管您可能会考虑将参数input_prompt 添加到user_enters_number)。

      这种“分而治之”的方法没有性能损失:智能编译器会做任何必要的事情来优化代码,例如内联函数。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-06
        • 2012-03-28
        • 1970-01-01
        • 1970-01-01
        • 2011-05-08
        • 1970-01-01
        • 2020-04-18
        • 1970-01-01
        相关资源
        最近更新 更多