C++中使用switch..case语句的易出错陷阱和规避方法

C++作为C语言的升级版,支持很多C语言不支持的语法。例如,函数中的局部变量不必在函数的最开始统一定义了,在函数内部随时定义新的局部变量成为可能。

比如下面的示例代码,在for循环的初始条件中定义了用于计数的整形变量i,这是不符合C语言语法规定的,故而无法通过C语言编译器的编译。

int fun()
{
  int n = 6;
  
  for (int i = 0; i < 3; i++)
  {
    n += i;
  }
  
  return n;
}

必须把代码修改为如下所示,在函数的开头定义整形变量i,才能通过编译。

int fun()
{
  int n = 6;
  int i = 0;

  for (i = 0; i < 3; i++)
  {
    n += i;
  }

  return n;
}

不过,如果使用C++编译器,以上两段代码都是符合语法规定的,都可以通过编译。

回到主题,这里要说一个C++在语法方便的同时带来的隐患。来看这一段C++的swtich..case代码:

void fun(int nInput)
{ 
  switch(nInput)
  {
  case 1:
    int n;
    n = 1;
    printf("case1");
    break;
  case 2:
    printf("case2");
    break;
  default:
    printf("case defalut");
    break;
  }
}

int main(int argc, char* argv[])
{
  fun(8);
  return 0;
}

这段代码利用了刚才提到的C++新的语法支持,在switch..case的分支case 1中,定义了整形变量n,并且把它赋值为1。这段代码完全合法,编译通过(VS2012环境中),运行结果如下图所示,一切正常。

问题出现在当我们尝试初始化整形变量n的时候。定义变量的同时初始化,是一个好习惯,然而,此时对n的初始化却会引发错误导致无法编译。修改示例代码中的case 1部分,尝试把整形变量n初始化为0:

void fun(int nInput)
{ 
  switch(nInput)
  {
  case 1:
    int n = 0;
    n = 1;
    printf("case1");
    break;
  case 2:
    printf("case2");
    break;
  default:
    printf("case defalut");
    break;
  }
}

int main(int argc, char* argv[])
{
  fun(8);
  return 0;
}

编译报错如下图所示。大致意思是说n的初始化操作被跳过了。

回想函数调用过程,在函数的参数、当前代码地址、栈地址入栈之后,紧接着系统会给函数内部的局部变量在栈里划分一片空间,这片划分出来的空间入栈之后,系统会给所有被初始化的局部变量赋予初始值。

如此一来,在示例代码中的情况下,C++编译器就不知所措了。整形变量n的作用域是swtich..case结构被花括号括起来的整个部分:虽然整形变量n的定义在case 1标签下面,但它对于case 2和case default都是可见的,可以把case 2和case default理解为整形变量n的“利益相关者”。站在编译器的角度,如果对整形变量n进行初始化操作,那么则相当于默认switch..case会跳转到case 1标签下,这显然是一种置case 2和case default于不顾的非法行为;如果不进行初始化操作,那么编译器就没有完整翻译程序源代码,没有完成自己的职责。在这种两难境地下,编译器只好选择报错了。

有没有一种解决方案,既能让我们充分利用C++灵活的语法规定(在switch..case结构内部也可以定义局部变量),又能够让我们保持定义局部变量后立即初始化的良好习惯,而且还不让编译器为难(报错)呢?

答案是有的!解决思路是把定义的局部变量隔绝起来,达到缩小局部变量作用域的效果,也就是让其他case分支看不到它。正所谓“眼不见心不烦”,其他的分支看不到这个局部变量,也就不会产生什么意见了。具体的解决方案就是在每一个case的标签下面都嵌入一对花括号。

修改后的代码如下所示:

void fun(int nInput)
{ 
  switch(nInput)
  {
  case 1:
    {
    int n = 0;
    n = 1;
    printf("case1");
    break;
    }
  case 2:
    {
    printf("case2");
    break;
    }
  default:
    {
    printf("case defalut");
    break;
    }
  }
}

int main(int argc, char* argv[])
{
  fun(8);
  return 0;
}

经测试,编译通过,如下图所示:

当然,case 2和case defalut的下面不是必须要加一对花括号,因为它们下面并没有局部变量的定义和初始化操作。但是,在所有case label下都加上一对花括号是一个很好的习惯,因为随着代码量的增加,万一这个label下面发生了变量的定义和初始化操作而没有引起注意的话,调试起来可能会很麻烦。在那种情况下,编译器的提示信息可能不会像本文示例这样给出明确的错误描述,而是可能会因为上下文环境的原因而给出晦涩不清的错误描述,让人一下看不出问题到底出在了哪里。

总结:在享受C++语法便利的同时,不要忘记了这些潜在的小陷阱,灵活利用花括号来限制局部变量的作用域。

posted @ 2016-10-10 23:25  zhugehq  阅读(4691)  评论(0编辑  收藏  举报