假设:
switch ( test ) {
// Set some variables, call some functions ?
int x = 1 ;
int y = function(x) ;
//
case 1 :
// Process for test = 1
...
break;
case 5 :
// Process for test = 5
...
break;
default :
// Process for all other cases.
...
}
执行我在第一个case
之前添加的额外代码是“合法的”吗?我在例子中从未见过这个。
答案 0 :(得分:12)
首先介绍switch
(真的)的工作原理:
switch
通常被认为是一个构造,它根据某个表达式的值选择要执行的代码片段,如
switch (x) {
case 1:
foo();
break;
case 2:
bar();
break;
}
但是,将switch
视为计算goto
语句的一种形式更为准确。例如,以下内容完全合法:
switch (x) {
puts("I can't be reached");
case 1:
if (cond) {
case 2:
puts("Either x == 1 && cond, or x == 2");
}
}
根据x
的值,如果case 1
既不是1,也不是case 2
,程序将跳转到switch
或x
(或超过x
2)。
您的程序将编译为C(y
中的switch
和case
的垃圾值,因为初始化被跳过),但不是C ++。原因是C ++不允许跳转到int
标签来跨越变量的初始化。对于像int x;
这样的简单类型,允许跳过int x = 1;
(因为不涉及初始化),但不跳过case
。
这种差异的主要动机可能是,当涉及构造函数时,跳转到{C} +中的初始化的case
标签是不安全的。例如,如果C ++允许在某个范围内的定义My_class my_object
之后发生case
标签,那么跳转到my_object
标签将跳过goto
的构造函数但仍然运行退出范围时的析构函数。
相同的限制适用于C ++中的switch
。您无法使用它跳转到块并通过变量初始化。
作为旁注,if
遵循与while
和if
相同的一般语法。 C11标准(ISO / IEC 9899:2011,第6.8.4节)中给出的switch
语法是
if (表达式)声明
,而case
的语法是
切换(表达式)声明
关于语句的唯一区别(在C-C ++中添加了一些如上所述的限制)是允许它包含break
标签(和{{ 1}})对于switch
而不是if
(除非if
出现在switch
内)。
与if
一样,您甚至可以不用括号来编写如下代码。 (这是不必要的混淆是另一种讨论。)
switch (x) case 1: case 2: puts("x is 1 or 2");
从语法上讲,case
和default
标签与goto
标签属于同一类别。 C11标准的第6.8.1节有以下定义:
标记的语句:
标识符 : 声明
案例 常量表达式 : 声明
默认 : 声明
答案 1 :(得分:7)
您可以通过简单的测试了解会发生什么:
int w = 1;
switch (w)
{
int i = 3;
int y = foo(i);
case 1:
printf("here %d\n", y);
printf("here %d\n", i);
break;
case 2:
printf("not here\n");
break;
}
此代码将使用gcc
在函数内部编译。 C编译器看到i
和y
在由大括号分隔的块内声明,因此将接受它。但是,printf
语句将打印i
和y
的垃圾,因为从不执行任务。这是因为switch
语句形成了case
的跳转,该跳转对应于switch
头部的表达式。因此无法到达左大括号和第一个case
之间的可执行代码。请参阅Why can't variables be declared in a switch statement?,它没有完全解释相同的情况,但确实有关于switch
语句的一些相关讨论。
如果您在打开警告(gcc -Wall
)的情况下进行编译,则会得到:
foo.c: In function ‘main’:
foo.c:19:15: warning: ‘y’ may be used uninitialized in this function [-Wuninitialized]
foo.c:20:15: warning: ‘i’ may be used uninitialized in this function [-Wuninitialized]
有趣的是,以下代码将在没有警告和编译的情况下编译:
int w = 1;
switch (w)
{
int i;
int y;
case 1:
i = 2; y = 3 * i;
printf("here %d\n", y);
printf("here %d\n", i);
break;
case 2:
i = 1; y = 2;
printf("here %d\n", y);
printf("here %d\n", i);
break;
}
变量按照您所期望的那样打印,因为它们是在switch
块的范围内声明的,并且在执行发生的case
部分中设置的值。在这种情况下它起作用的事实并不是说它是推荐的做法。 :)
答案 2 :(得分:3)
控件可以到达switch
头部和第一个case
表达式之间的语句 - 但其他一些控件结构必须发送它那里。例如,没有什么可以阻止你interlacing a switch and a loop:
#include <stdio.h>
int main(void)
{
int x = 1;
switch (x) {
do {
printf("got here with x=%d\n", x);
case 2:
puts("case two or loop");
case 1:
puts("case one or loop");
} while (++x < 3);
}
return 0;
}
在-std=c11
模式下没有任何来自gcc 4.9或clang 3.5的投诉进行编译,并且警告随着时间的推移而上升,并且在运行时打印:
case one or loop
got here with x=2
case two or loop
case one or loop
有趣的事实:启用优化后,两个编译器实际上都会生成100%的直线代码:
main:
subq $8, %rsp
movl $.LC0, %edi
call puts
movl $2, %esi
movl $.LC1, %edi
xorl %eax, %eax
call printf
movl $.LC2, %edi
call puts
movl $.LC0, %edi
call puts
xorl %eax, %eax
addq $8, %rsp
ret
答案 3 :(得分:1)
这是不合法的,当你编译时,你会得到一堆“十字架初始化...”错误。对这里发生的事情有一个非常好的解释: Getting a bunch of crosses initialization error
本质上,switch case通过跳过case语句之间的任何代码来工作,跳过变量初始化是违法的。它在技术上是合法的,并且如果你只是在两者之间放置一个函数调用就会编译,尽管函数永远不会被调用。
int test = 1;
int x = 1 ;
switch ( test ) {
//Technically legal, although will never be called.
std::cout<<somefunc(x)<<std::endl;;
//jumps straight here, over any code above it
case 1 :
std::cout<<"1"<<std::endl;
break;
case 5 :
std::cout<<"5"<<std::endl;
break;
default :
std::cout<<"Default."<<std::endl;
}
答案 4 :(得分:0)
我尝试编译类似于你问的代码,它至少出现了一个错误:
错误C2360:初始化&#39; x&#39;被&#39; case&#39;跳过标签
您的额外代码从未执行过,因为在切换后检查语句&#39; test&#39;,然后它将转到相应的标签,您的额外代码不在任何标签下,因此它是永远的被跳过了。
为什么你不能在switch语句之前添加额外的代码?有什么原因吗?