我可以将代码放在案例之外的交换机中吗?
假设:
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
之前添加的额外代码是“合法的”吗? 我在例子中从未见过这个。
首先介绍一下switch
(真的)如何工作:
switch
通常被认为是一个构造,它根据某个表达式的值选择要执行的代码片段,如
switch (x) { case 1: foo(); break; case 2: bar(); break; }
但是,将switch
视为计算goto
语句的一种forms更为准确。 例如,以下内容完全合法:
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
或case 2
(如果x
既不是1也不是2,则跳过switch
)。
您的程序将编译为C(在switch
内部包含x
和y
垃圾值,因为跳过了初始化),但不是C ++。 原因是C ++不允许跳转到case
标签来跨越变量的初始化。 对于像int
这样的简单类型,跳过int x;
允许(因为不涉及初始化),但不跳过int x = 1;
。
这种差异的主要动机可能是,当涉及构造函数时,跳转到C ++中的初始化的case
标签是不安全的。 例如,如果C ++允许在某个范围内定义My_class my_object
之后发生case
标签,那么跳转到该case
标签将跳过my_object
的构造函数,但在退出范围时仍然运行其析构函数。
同样的限制适用于C ++中的goto
。 您无法使用它跳转到块并通过变量初始化。
作为旁注, switch
遵循与if
和while
相同的一般语法。 if
如C11标准(ISO / IEC 9899:2011,第6.8.4节)中给出的语法是
if (表达式)语句
,而switch
的语法是
switch (表达式)语句
关于语句的唯一区别(在C-C ++中增加了一些如上所述的限制)是允许它包含一个switch
case
标签(和break
)但不包含if
(除非if
发生在if
中)一个switch
)。
就像使用if
,您甚至可以省去大括号并编写如下代码。 (这是否会造成不必要的混乱是另一种讨论。)
switch (x) case 1: case 2: puts("x is 1 or 2");
在语法上, case
和default
标签与goto
标签属于同一类别。 C11标准的第6.8.1节有以下定义:
标记的语句:
identifier : 声明
case constant-expression : statement
默认 : 声明
您可以通过简单的测试了解会发生什么:
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
在函数内部gcc
。 C编译器看到i
和y
在由大括号分隔的块内声明,因此将接受它。 但是, printf
语句将为i
和y
打印垃圾,因为从不执行赋值。 这是因为switch
语句形成了跳转到与switch
头部表达式相对应的case
。 因此无法到达左大括号和第一个case
之间的可执行代码。 请参阅为什么不能在switch语句中声明变量? ,它没有解释完全相同的情况,但确实有一些关于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
部分中设置了值。 在这种情况下它起作用的事实并不是说它是推荐的做法。 🙂
控制可以到达switch
头和第一个case
表达式之间的语句 – 但是其他一些控制结构必须将它发送到那里。 例如,没有什么能阻止你交换开关和循环 :
#include 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; }
编译时没有任何来自gcc 4.9或clang 3.5 in -std=c11
模式的投诉,并且警告会随着它们的高速而上升,并且在运行时打印:
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
这是不合法的,当你编译时,你会得到一堆“十字架初始化…”错误。 对这里发生的事情有一个非常好的解释: 获得一堆十字架初始化错误
本质上,switch case通过跳过case语句之间的任何代码来工作,跳过变量的初始化是违法的。 它在技术上是合法的,如果你只是在两者之间放一个函数调用,它将编译,虽然函数永远不会被实际调用。
int test = 1; int x = 1 ; switch ( test ) { //Technically legal, although will never be called. std::cout<
我尝试编译类似于你问的代码,它至少出现一个错误:
错误C2360:’case’标签跳过’x’的初始化
您的额外代码从未被执行过,因为在切换检查语句’test’之后,它将转到相应的标签,您的额外代码不在任何标签下,因此它永远被跳过。
为什么你不能在switch语句之前添加额外的代码? 任何原因?