在case语句中使用{}。 为什么?
在case
语句中使用{
和}
有什么意义? 通常情况下,无论case
语句中有多less行,都会执行所有的行。 这只是一个关于较旧/较新的编译器的规则还是有一些背后的东西?
int a = 0; switch (a) { case 0:{ std::cout << "line1\n"; std::cout << "line2\n"; break; } }
和
int a = 0; switch (a) { case 0: std::cout << "line1\n"; std::cout << "line2\n"; break; }
{}
表示一个新的范围块。
考虑以下非常人为的例子:
switch (a) { case 42: int x = GetSomeValue(); return a * x; case 1337: int x = GetSomeOtherValue(); //ERROR return a * x; }
你会得到一个编译器错误,因为x
已经在范围中定义了。
将它们分离到它们自己的子范围将消除在switch语句之外声明x
的需要。
switch (a) { case 42: { int x = GetSomeValue(); return a * x; } case 1337: { int x = GetSomeOtherValue(); //OK return a * x; } }
TL; DR
你可以用一个初始化器或一个不重要的对象来声明一个variables的唯一方法就是引入一个块作用域,使用{}
或其他控制结构,这个控制结构有自己的作用域,就像循环或if语句一样 。
血淋淋的细节
我们可以看到, case只是标签语句,就像goto语句中使用的标签一样( 这在C ++草案标准 6.1节的Labeled语句中有介绍 ),从6.7
节我们可以看到跳过一个声明是不允许的情况下,包括那些初始化:
可以将其转换为块,但不能以绕过具有初始化的声明的方式。 从自动存储持续时间的variables不在范围内的点跳转到87所在的点的程序是不合格的,除非variables具有标量types,具有简单默认构造函数的类types和简单的析构函数,这些types之一的cv限定版本,或前面types之一的数组,并且声明时没有初始值设定项(8.5)。
并提供了这个例子:
void f() { // ... goto lx; // ill-formed: jump into scope of a ly: X a = 1; // ... lx: goto ly; // OK, jump implies destructor // call for a followed by construction // again immediately following label ly }
请注意,这里有一些微妙之处,你可以跳过一个没有初始化的标量声明 ,例如:
switch( n ) { int x ; //int x = 10 ; case 0: x = 0 ; break; case 1: x = 1 ; break; default: x = 100 ; break ; }
是完全有效的( 现场示例 )。 当然,如果你想在每种情况下声明相同的variables,那么他们每个都需要自己的作用域,但是在switch语句之外它也是以相同的方式工作的,所以这不应该是一个大的惊喜。
至于不允许跳过初始化的原理, 缺陷报告467虽然涵盖了一个稍微不同的问题,但为自动variables提供了合理的情况:
自动variables,如果没有明确初始化,可以有不确定的(“垃圾”)值,包括陷阱表示,
这可能是更有趣的,看看你在一个开关范围内扩展范围的情况下 ,最有名的例子可能是达夫的设备 ,看起来像这样:
void send( int *to, const int *from, int count) { int n = (count + 7) / 8; switch(count % 8) { case 0: do { *to = *from++; // <- Scope start case 7: *to = *from++; case 6: *to = *from++; case 5: *to = *from++; case 4: *to = *from++; case 3: *to = *from++; case 2: *to = *from++; case 1: *to = *from++; } while(--n > 0); // <- Scope end } }
这是一种习惯,允许您将variables声明与析构函数(或范围冲突)一起插入到case
子句中。 另一种看待它的方式是他们正在为他们希望拥有的语言写作,所有的stream程控制都由块组成,而不是由语句序列组成。
检查这个基本的编译器限制,你会开始想知道发生了什么:
int c; c=1; switch(c) { case 1: //{ int *i = new int; *i =1; cout<<*i; break; //} default : cout<<"def"; }
这会给你一个错误:
error: jump to case label [-fpermissive] error: crosses initialization of 'int* i'
虽然这一个不会:
int c; c=1; switch(c) { case 1: { int *i = new int; *i =1; cout<<*i; break; } default : cout<<"def"; }
在开关中使用括号表示Rotem所说的一个新的范围块。
但是当你阅读的时候也可以清楚地expression出来。 要知道案件在哪里停下来,因为你可能有条件的rest。
原因可能是:
- 可读性,它可视化地增强每个案例作为一个范围的部分。
- 为几个开关情况声明一个具有相同名称的不同variables。
- 微观优化 – 只要你离开案例的范围,甚至是“GOTO”命令用法的更细面条情景,你都希望破坏一个非常昂贵的资源分配variables。