switch case语句是非常常用的语句,入门的码农也知道是做什么的。
但关于switch case内定义变量的问题,网上的很多博文都有谬误,在这里我写一下对这个语句的了解。
一
先看合法的定义方式:
int main(int argc, const char * argv[]) {
int idx = 2;
switch (idx) {
int k;
case 1:
int j;
break;
case 2:
k = 1;
j = 2;
std::cout<<"K:"<<k<<std::endl;
std::cout<<"J:"<<j<<std::endl;
break;
default:
break;
}
return 0;
}
在C++11 std Dialect下 打印出的结果是:
K:1
J:2
二
然后看看不合法的写法:
int main(int argc, const char * argv[]) {
int idx = 2;
switch (idx) {
int k = 1;
case 1:
int j = 1;
break;
case 2:
k = 1;
j = 2;
std::cout<<"K:"<<k<<std::endl;
std::cout<<"J:"<<j<<std::endl;
break;
default:
break;
}
return 0;
}
编译无法通过。
三
解释下原因:
一为什么合法?
首先一个变量有没有被定义是在编译时就检查过的,因此这里可以编译通过。
其次在处理switch case语句中,C++11标准的编译器都会在执行case跳转前为变量分配空间,因此执行也没有问题。
(当然我并不建议在case语句外定义变量,因为何时为变量分配空间是编译器特性,而非语言特性,虽然这个问题中遵守C++11标准的编译器没有问题)
二为什么不合法?
C++11标准禁止这种写法。在编译时,编译器就会报错。C++11禁止这种写法的原因来自于C++的一个规定:
It is possible to transfer into a block, but not in a way that bypasses declarations with initialization. A program that jumps from a point where a local variable with automatic storage duration is not in scope to a point where it is in scope is ill-formed unless the variable has POD type (3.9) and is declared without an initializer. (The transfer from the condition of a switch statement to a case label is considered a jump in this respect.)
众所周知,C++不允许使用未初始化的变量,而初始化操作和定义变量对于编译器来说是两码事,初始化操作是一个确确实实的在运行时才会被调用的语句,是可以被case跳转屏蔽掉的语句,而定义则是在编译器就完成检查的。
如果二的写法合法的话,那么会发生这样的问题:
编译时定义变量被执行,初始化和赋值语句也用各自的方式编译完成,并且在运行时,程序也的确为此变量分配了空间。但是在运行时,初始化语句却在某些情况下被跳转掉了。
虽然对于int等类型的变量,赋值和初始化在我们看来都是"=",但编译后的二进制是完全不同的,编译器会在编译时把"int j = 1;"编译成初始化,而把"j = 1;"编译成赋值。因此在整个运行过程中,如果初始化语句被case跳转掉了,我们在其他case语句中的"="不可能对此变量进行初始化。
这时,如果在整个switch(){}的定义域中,有对此变量的调用,那就是『试图在初始化一个变量前使用它』,因此C++11禁止了这种写法。
四
额外谈一些:
如果不在case中加上{},整个switch(){}都是使用同一个作用域,这一点通过上文符合语法例子中对J、K的定义和使用就能看出来。最好的方法就是在编码时,将整个switch语句用到的变量在switch外声明,并且针对某个case需要单独使用变量的情况,用{}明确此case语句的作用域
case的实现与goto非常相似,case的本质是一种标签,在switch case语句中变量的定义问题可以推广到goto语句中。
下面的代码也是不合法的,因为string是一个类,类有自己的隐式初始化方法,实际上这依然是个可能被跳转掉的初始化语句。
case 1:
std::string tempStr;
break;