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++语法便利的同时,不要忘记了这些潜在的小陷阱,灵活利用花括号来限制局部变量的作用域。