众所周知,C++的switch语句用于多分支选择,switch语句提供了一条便利的途径使我们能够在若干固定选项中做出选择。看似方便,用起来也确实方便,但若是没有经验,不注重细节问题,会踩很多坑,有时候还会因为看不懂编译器的报错备受折磨。
先举一个简单的例子:
#include <iostream>
using namespace std;
int main() {
char ch = 'A';
switch (ch)
{
case'A':
cout << "It's A." << endl;
break;
case'B':
cout << "It's B." << endl;
break;
case'C':
cout << "It's C." << endl;
break;
case'D':
cout << "It's D." << endl;
break;
default:
break;
}
return 0;
}
程序很简单,给ch一个值,ch是什么就输出对应的语句,例如程序中,让ch=‘A’,那么运行后会输出"It's A."
break的作用
break的作用是中断这条switch语句,从break之后,从switch(){}整个区域之后的下一条语句开始,例如上文中的程序switch语句之后的下一条语句是return 0,因此在switch触发break之后,就执行return 0,(众所周知,整个switch是要看成一条语句的)
然后现在我们去掉case'A'里面的break来运行看看结果:
其余上下文与之前相同
......
case'A':
cout << "It's A." << endl;
//break;
......
看得出来,执行完case'A'分支内的语句之后没有停下,而是继续执行了case'B'内的分支,直到遇到case'B'分支内的break才结束。根据使用经验,这虽然是我们容易犯的一个错误(指的是:漏写break)但有时候也确实需要故意不写break。
接下来是另一个坑点:
switch内部的变量定义
简单来说就是在case分支内定义变量,这个错误非常的折磨人,我第一次遇到这个报错的时候,完全不能理解。下面来看一下问题出现的情景:
#include <iostream>
using namespace std;
int main() {
char ch = 'B';
switch (ch)
{
case'A':
int x = 10;
cout << "case A" << endl;
break;
case 'B':
cout << "case B" << endl;
break;
}
return 0;
}
就是上面这样一段简单的代码,会给你报一个错:
这是因为,整个switch内是同一个作用域,如果在switch的某个分支内部初始化一个变量,那么这个初始化就可能会被跳过,之后就有可能在未初始化的情况下使用这个变量,因此这种行为不合理。
简单来说就是有可能由于case分支跳过这个变量的初始化。
然而接下来又出现让人想不通的地方。
下面这段程序,不会报刚刚的错误,会输出正确的结果。总之就有了一个匪夷所思的地方,在case‘A’内部定义的变量,在case‘B’内仍然可见,因此可以得出一个结论,switch语句并不是直接跳过了case'A',而是执行了case'A'内部的定义x的语句,再转到case'B'分支
#include <iostream>
using namespace std;
int main() {
char ch = 'B';
switch (ch)
{
case'A':
int x;//x只定义不初始化
cout << "case A" << endl;
break;
case 'B':
x = 10;//给x初始化
cout << x << endl;
break;
}
return 0;
}
为了验证,我们简单修改一下上面的程序,去掉了原本x的初始化操作,然后运行查看一下结果
switch (ch)
{
case'A':
int x;//x只定义不初始化
cout << "case A" << endl;
break;
case 'B':
cout << x << endl;
break;
}
不出意料报了一个没有初始化的错误。
这里已经可以简单得出结论,switch语句会跳过初始化,但是不会跳过变量定义。
接下来我们再看一个例子:
#include <iostream>
#include<string>
using namespace std;
int main() {
char ch = 'B';
switch (ch)
{
case'A':
string s;//定义了一个空字符串
cout << "case A" << endl;
break;
case 'B':
break;
}
return 0;
}
上面的语句,仍然会报同样的错误:
按照之前的逻辑,只是定义的话,似乎不会报错,但事实上,例如string等类型(具有默认构造函数)会进行默认的初始化,也就是说在定义的时候就已经被初始化了。因此仍然会报初始化被跳过的错误。
解决方案
如果想在case分支内部定义并且使用变量,可以使用一对{}框起来。
#include <iostream>
#include<string>
using namespace std;
int main() {
char ch = 'A';
switch (ch)
{
case'A':
{
string s = "hell world!";
cout << s << endl;
}
break;
case'B':
break;
}
return 0;
}
在C++里面由{}包含起来的语句块,会被看做一条语句,这样就不会报变量初始化被跳过的错误,因为上面程序中s的作用域仅仅在{}内。同时这样会出现一个问题,就是无法在后面的分支使用这个变量。
接下来我们来看一种除了使用语句块,可以在分支内部定义并且初始化变量的情况:就是当定义所在的分支是switch语句中最后一条分支的时候,可以在其中初始化变量。
#include <iostream>
#include<string>
using namespace std;
int main() {
char ch = 'A';
switch (ch)
{
case 'A':
string str = "hello";
break;
}
return 0;
}
最后我们来看一个bug,我实在visual studio环境下运行的,首先看场景1:
#include <iostream>
#include<string>
using namespace std;
int main() {
char ch = 'C';
switch (ch)
{
case'A':
int x;
case'B':
cout << x << endl;
case'C':
cout << x << endl;
}
return 0;
}
在A分支定义,然后不初始化,跳到C分支:发现报错
然后我们稍作修改:
#include <iostream>
#include<string>
using namespace std;
int main() {
char ch = 'C';
switch (ch)
{
case'A':
int x;
case'B':
x = 10;//在这里加上X的初始化,但是仍然选择C分支
cout << x << endl;
case'C':
cout << x << endl;
}
return 0;
}
查看运行结果:
输出了一个未初始化的随机值
这里我猜测,是在其他分支的初始化,骗过了visual studio的编译器,让它以为变量被初始化了。但实际上还是没有。其他有些编译器(例如Dev C++),在遇到未初始化的变量的时候,不会报错,最多给一个警告。
总结:
switch语句虽然好用,但其实坑点非常多一个是break的问题,一个是变量定义相关,有关变量定义的问题。我再次简单总结一下:在switch语句任何地方定义变量(没有用{}包含起来),这个变量的作用域就是这条定义语句之后的所有区域,这个变量对这条语句接下来的区域可见的。所以不能再变量定义的时候初始化,这样会报一个初始化被跳过的错误,有一种掩耳盗铃的方法就是只定义不初始化,或者是在同一个分支内先定义再初始化欺骗编译器(对于会进行默认初始化的变量,不能做到只定义不初始化)。但最好还是不建议这样,建议不要再分支内定义或者使用变量,如果一定要定义变量,尽量限制在单个分支内,因此使用{}把变量定义限制在一个语句块之内。