在这种情况下,我需要一个“默认”语句吗?

4

在学校里,我们的讲师常常告诉我们在 switch case 语句中总是要包含一个 Default 语句。然而,我一直在想是否对于所有(或大多数)情况都是必需的?

考虑以下 C++ 示例:

int num = rand()%3;
switch (num)
{
   case 0: methodOne();
           break;
   case 1: methodTwo();
           break;
   case 2: methodThree();
           break;
}

在上述情况下,我认为不可能出现> 2或<0的情况,那么我还需要包括一个Default语句吗?
在SO中有类似的问题问是否需要在switch-case中包括Default。那里给出的回复是几乎任何时候都应该包含Default。但根据我个人遇到的所有情况来看,这似乎是多余的,因为永远不会到达Default编辑:此外,在防御性编程方面,这种情况需要一个Default语句吗? 如果我要添加一个Default语句。它将只是一个错误处理语句,我的说法正确吗?

1
在这种特定情况下,不需要包含default部分,因为正如您已经指出的那样,num没有机会具有不同的值。 - mic4ael
int i = 2; if (i == 2) { //... }。你需要另一个else语句吗? - 4pie0
1
仅作为注意事项,n%3有时可能是负数,但如果它来自rand,则不会。 - soandos
6个回答

6

从技术上讲,你不需要在switch语句中包含default,因为你已经涵盖了所有可能的情况。

然而,我总是觉得在default中包含一个断言/异常很有用。考虑以下情况:

// V1.0.0: Initial version.
int num = rand()%3;
switch (num)
{
   case 0: methodOne();
           break;
   case 1: methodTwo();
           break;
   case 2: methodThree();
           break;
}

稍后...

// V1.0.0: Initial version.
// V1.0.1: Added a fourth method.
int num = rand()%4;
switch (num)
{
   case 0: methodOne();
           break;
   case 1: methodTwo();
           break;
   case 2: methodThree();
           break;
}

在这种情况下,第二位开发者更新了rand的模数,但实际上并没有添加处理num == 4的情况。如果没有default,你将会默默地失败,这可能会导致各种糟糕的后果,而这些后果可能非常难以调试。更可维护的解决方案可能是:
// V1.0.0: Initial version.
// V1.0.1: Added a fourth method.
int num = rand()%4;
switch (num)
{
   case 0: methodOne();
           break;
   case 1: methodTwo();
           break;
   case 2: methodThree();
           break;
   default:
           assert(false);
           throw InvalidNumException("BUG: Method has not been specified for value of num");
}

调试时,这将在断言处停止调试。如果(天哪)缺少case一直到生产环境,你将会得到一个异常抛出,而不是运行并做一些不应该发生的事情。

编辑:

我认为包括一个万能处理是防御性编程风格的一个很好的补充。如果你错过了一个case语句,它保证你会得到有用的结果(即使这个有用的结果是导致程序崩溃)。

编辑2:

正如Andre Kostur在对此答案的评论中提到的,一些编译器会发出警告,如果你开启了一个枚举并忘记处理一个case,这就是不为枚举开关语句包括一个default案例的一个很好的理由。有关更多信息,请参见Phresnel's answer


这是最好的答案,因为在实际编码中,防御性编程应该是强制性的,因为维护可能会将系统转换为其他东西。 - user2672165
1
是的和不是的。如果我在枚举中进行切换,各种现代编译器将会警告您是否没有在case语句中涵盖每个枚举值。通过提供一个默认子句,它将抑制此警告。这有助于未来的开发,因为如果您添加了新的枚举值,编译器将告诉您需要调整的switch语句。 - Andre Kostur
@AndreKostur - 您说得完全正确。我本来想编辑我的答案谈论枚举,但我认为phresnel已经很好地涵盖了它,所以我认为重复没有什么意义。不过,我会在我的答案中添加一个指针 :-) - Karl Nicoll

3

这不是必需的,但将其与打印一起包含是一个好习惯。我总是在默认(或except)中执行类似于print“这不应该发生”的操作,以便了解是否发生了意外情况。计算机有时会出现奇怪的问题,需要做好准备!


这是维护预防措施。也许几年后会有变化,从而导致新情况的发生。 - user2672165

2

根据具体上下文,这可能是一种风格问题。我有时会采用以下方法。

考虑到在某个时间点您对其进行了微调。

int num = rand()%3;

为了

int num = rand()%4;

如果这种情况发生,那么您的 switch 语句就不再正确和完整。针对这种情况,您可以添加以下内容:

default:
    throw std::logic_error("Oh noes.");

std::logic_error是用于处理程序团队出现的错误。如果您的团队忘记更新switch,它将会(希望尽早)出现一个带有追踪信息的终止程序,以便进行排查。

default 的缺点

包含default子句也有一个缺点。当您在enum上执行switch时...

enum class Color {
    Red, Green, Blue
};

....

Color c = ....;
switch(c) {
case Color::Red: break; 
case Color::Green: break;
};

有些编译器会警告您没有涵盖所有的情况。为了让编译器安静下来,现在您可以做两件事:

Color c = ....;
switch(c) {
case Color::Red: break;
case Color::Green: break;
default: break;
};

Color c = ....;
switch(c) {
case Color::Red:  break;
case Color::Green:  break;
case Color::Blue:  break;
};

你会发现,这两种选择中后者可能更有生产力。但是,这取决于编译器的行为。你仍然可以在默认情况下抛出异常,但将一个本应是好的编译时错误转化为运行时错误,许多人认为前者更可取。
使用早期退出或结构体可以实现两全其美(可移植的错误,加上编译时错误的奖励),之后您可以测试是否命中了某个case。
Color c = ....;
switch(c) {
case Color::Red: return;
case Color::Green: return;
};
throw std::logic_error(...);


MYSQL mysql = {0};
switch(c) {
case Color::Red: mysql = red_database(); break;
case Color::Green: mysql = green_database(); break;
};
if (!mysql)
    throw std::logic_error(...);

2

我还需要包含一个default语句吗?

只要假设可能的num范围成立,你就不需要。只要你不改变计算它的代码,这个假设就会成立。

如果你想在将来验证这个假设是否改变,你可能会想要加上default语句。这种防御性编程可以快速捕捉到错误的假设,避免引起重大问题。

如果我添加了一个Default语句,它只会是一个错误处理语句,我说得对吗?

是的。我会抛出一个logic_error,或者直接终止程序,以表明逻辑假设无效。


用户实际上询问的是关于num可能范围的假设是否成立。 - 4pie0
@lizusek:不是很确定,但我已经澄清它确实成立。 - Mike Seymour

2

严格来说,你不需要这样做。但是,在更大的项目中,如果您以后想要更改代码,则可以帮助查找或避免错误。

例如,想象一下您以后想要添加一些情况/方法。如果您保留switch不变,则可能需要调试并查看为何未调用新方法。另一方面,在那里抛出一种NotImplementedException将直接导致您忘记添加一个情况。


0

那么让我们将您的代码进化一下,变得更加现实:

void processNumber(int maxNum) {
   int num = rand()%maxNum;
   switch (num)
   {
      case 0: methodOne();
           break;
      case 1: methodTwo();
           break;
      case 2: methodThree();
           break;
   }
}

在这里,您需要确保它在允许的值集合[1, 2, 3]中。您可以通过多种方式进行检查,但是您需要谨慎检查输入并引发错误,即使它是内部函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接