【问题标题】:Using if statement instead of default of switch case使用 if 语句代替 switch case 的默认值
【发布时间】:2016-05-15 21:32:04
【问题描述】:

在打开 switch case 之前使用 if 语句并避免使用 default 关键字是否正确? 例如,我想要一个程序,它将月份的数字作为输入并告诉你它的名称。这是使用switch case 语句的代码:

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

main() {
    int month;
    printf("Insert the number of the month and the program will return its name");
    scanf("%i", &month);
    switch (month) {
      case (1):
        printf("The month is January");
        break;
      case (2):
        printf("The month is February");
        break;
      case (3):
        printf("The month is March");
        break;
      case (4):
        printf("The month is April");
        break;
      case (5):
        printf("The month is May");
        break;
      case (6):
        printf("The month is June");
        break;
      case (7):
        printf("The month is July");
        break;
      case (8):
        printf("The month is August");
        break;
      case (9):
        printf("The month is September");
        break;
      case (10):
        printf("The month is October");
        break;
      case (11):
        printf("The month is November");
        break;
      case (12):
        printf("The month is December");
        break;
      default:
        printf("not valid");
    }
    system("pause");
    return 0;
}

然后,我想知道是否可以将无效条件放在 if 语句中,而不是放在 default 关键字中。对我来说这似乎是正确的,因为我想在程序执行 switch case 语句之前验证该值。你怎么看,会是正确的吗?如果我没有问太多,请你告诉我为什么?

带有if语句的代码:

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

main() {
    int month;
    printf("Insert the number of the month and the program will return its name");
    scanf("%i", &month);
    if (month >= 1 && month <= 12) {
        switch (month) {
          case (1):
            printf("The month is January");
            break;
          case (2):
            printf("The month is February");
            break;
          case (3):
            printf("The month is March");
            break;
          case (4):
            printf("The month is April");
            break;
          case (5):
            printf("The month is May");
            break;
          case (6):
            printf("The month is June");
            break;
          case (7):
            printf("The month is July");
            break;
          case (8):
            printf("The month is August");
            break;
          case (9):
            printf("The month is September");
            break;
          case (10):
            printf("The month is October");
            break;
          case (11):
            printf("The month is November");
            break;
          case (12):
            printf("The month is December");
            break;
          default:;
        }
    } else {
        printf("not valid");
    }
    system("pause");
    return 0;
}

谢谢你,对不起我的英语,但它不是我的母语。如果我没有清楚地解释自己,请告诉我。

【问题讨论】:

  • 离题了,但是你最好还是看看数组来存储那些月份。你的代码会小很多...
  • 为什么return 没有括号,比如return (0)case 有括号?
  • 归根结底,双方都完成了工作。在我看来,第一个更干净,性能会稍微好一些,因为它需要的比较少。性能不会显着提高,打印比比较花费的时间要长得多,因此差异不应该是明显的。
  • @UlrichEckhardt:这些是括号,returncase 不需要。 OP 解释了为什么他认为它们很有用,但几乎没有人会这样做。

标签: c if-statement optimization switch-statement


【解决方案1】:

除了一些细节之外,这两种方法都是有效且等效的:

  • 您定义main 的方式已经完全过时了。它不会在严格模式下使用 C99 编译器进行编译。使用int main(void)int main(int argc, char *argv[])
  • 测试scanf()的返回值。如果您键入的内容无法解析为数字,scanf() 将返回 0,如果您关闭输入流,甚至返回 EOF,其余代码将使用未初始化的值 month。李>
  • case 子句中为值加上括号既没用也不惯用。删除它们。
  • switch 语句的最后一个子句的末尾添加break; 语句更安全,default 或不。如果您添加了另一个子句,您不会冒险省略它。
  • 从第二个代码中删除default:;子句,它没用而且令人惊讶。

可能更多地指示第二种方法的原因是,如果输入超出范围,您想要做一些完全不同的事情,例如重新启动输入操作。 if 语句将允许您正确区分这些情况,而仅使用 default 子句可能不太合适:

for (;;) {
    int n, month;
    printf("Enter a number between 1 and 12: ");
    n = scanf("%d", &month);
    if (n == EOF) {
        printf("Unexpected end of file\n");
        exit(1);
    }
    if (n != 1) {
        printf("Invalid input\n");
        scanf("%*[^\n]%*c"); /* flush the pending input */
        continue;
    }
    if (month >= 1 && month <= 12) {
        switch (month) {
          case 1: 
            printf("The month is January\n");
            break;
          ...
          case 12: 
            printf("The month is December\n");
            break;
        }
        handle_month(month);  // perform other tasks
        break;
    } else {
        printf("Invalid month number\n");
    }
}

【讨论】:

  • 括号没有错,表达式(12)仍然是一个常量整数表达式,但是它们很少用在case子句中。最好使用 惯用的 风格,避免用没有目的的不寻常的结构让读者感到惊讶。
  • @LorenzoGramigni:如果您觉得这个答案最合适,请随时单击答案分数下方的灰色复选标记以接受它。你还不能投票,因为你需要 15 的声望才能做到这一点。
  • 感谢您的回答。我把这些值加上括号是因为我认为它更正式。括号或引号仅仅是不必要的还是仅仅是不正确的?它们会影响代码吗?作为一个已解决的问题,我没有添加break;,也没有添加scanf() 的测试,无论如何感谢您的建议。既然我被告知if 方法是错误的,并且您说它是等效的,甚至性能不受影响,那么更多cases 会改变吗?有了更多的cases,使用if 语句不是更好吗,这样程序就不需要不必要地遍历所有cases
  • @LorenzoGramigni:程序不会遍历所有案例,如果案例值是连续的,没有或很小,它可以在恒定时间内使用直接调度指令间隙,或者它可以在log(N) 时间使用二进制查找。编译器生成高效代码,添加if 对性能影响很小或没有影响,它对程序流程和/或可读性很有用。案例的数量对switch 的性能影响很小或没有影响,无论如何性能对于错误案例并不重要。同样总是倾向于简单性、规律性、可读性而不是性能。
  • @LorenzoGramigni:如果您关心性能,请阅读这篇文章:codeproject.com/Articles/100473/…
【解决方案2】:

为什么不正确?它是。 default 更安全:如果你更改了cases,但没有更改if,你会遇到麻烦。

【讨论】:

    【解决方案3】:

    是的,您可以按照您的描述使用if 语句。但是,我认为这样做没有任何好处。

    【讨论】:

      【解决方案4】:

      原版更好。编译器会做几乎相同的工作,尽管由于分支预测,第二个会慢一些。

      但主要原因是代码的可维护性。添加或删除一个值时,您需要使用if 语句修改两个位置,而switch 仅修改一个位置。

      【讨论】:

      • 您对可维护性的评论是中肯的,但恐怕您对性能的说法是不真实的。 switch 这样的语句在两种情况下都可以编译为非常高效的代码。在某些编译器上,它甚至可能编译成完全相同的代码。
      • @chqrlie :问题是不太可能的块首先出现在第二个(如果)解决方案中。但是处理器分支预测器通常会降低向前跳转的概率。
      • 分支预测高度特定于处理器。由编译器生成有效的代码。您不能通过订购 if 分支来调整它。这将是过早的优化。请记住,这个switch 语句很可能会编译为对month-1 的单个测试和通过12 条目跳转表的间接跳转。在第二个代码中,if 测试可以编译成一个测试,如果编译器足够精明,它会注意到所有month 值都有一个跳转目标,所以只需要间接跳转。 可维护性是这里唯一重要的问题。
      • @chqrlie :这是特定的,但通常处理器更喜欢向后跳转。这是因为循环优化。我在这里并没有说这很重要,只是想指出最后可能会有所不同。第二点可能在这里不适用——在这两种解决方案中,编译器都足够聪明,可以避免多次比较,并且可能会在这里使用简单的数组。
      【解决方案5】:

      实际上,我会说您的代码在使用 switch 语句时违反了最佳实践。相反,试试这个:

      if (month >= 1 && month <= 12) {
          char const*const names[] = {
              "January",
              "February",
              ...
              "November",
              "December",
          };
          printf("The month is %s\n", names[month -1]);
      } else {
          printf("not valid\n");
      }
      

      也就是说,回到你最初的问题,在相同数据上混合流控制结构,如if/elseswitch/case 通常是不好的。这样做的原因是它不必要地复杂,所以要么坚持使用最初尝试中的单个 switch 语句,要么使用我上面概述的方法。

      【讨论】:

        【解决方案6】:

        我的个人观点,可能并不完美。

        拥有default 案例是首选,因为它可以捕获任何下落不明的案例。人们似乎依赖它,因此,gcc 中的警告标志 -Wswitch-default因此,有时您可能会在没有 default 大小写的情况下收到一些编译器警告。

        在您的特定情况下,这很好,但使用 default 案例是 good practice

        【讨论】:

        • assert((month &lt;= 0 || month &gt; 12) &amp;&amp; "invalid month value");: 恐怕这不是你的意思。
        • 请您详细说明一下
        • assert 中的表达式参数应该为真。你应该写assert(month &gt;= 1 &amp;&amp; month &lt;= 12)。它将编译为生成 if 的代码,并在条件为 false 时生成明确的错误消息,例如 assertion failed: month &gt;= 1 &amp;&amp; month &lt;= 12
        • 啊,谢谢,相应更新,我的想法无效。
        • 还要注意assert 将中止程序。这在这里可能不合适,因为 OP 想要打印特定消息,并调用系统 pause 命令使终端保持打开状态,直到用户按下键。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-11-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多