在案例陈述中使用{}。为什么?


101

什么是使用点{}在一个case声明?通常,无论一条case语句中有多少行,所有行都将执行。这仅仅是有关旧/新编译器的规则吗?

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;
}

57
一种用途是限制case语句中声明的变量的范围。
Abhishek Bansal


1
压痕也太多。这些案例只是switch语句块内的标签:它们不引入额外的嵌套,因此它们应与switch关键字对齐,在第二个示例中,封闭的语句仅缩进一次。请注意,您如何在后缩进四个缩进的空格break;
卡兹(Kaz)

请注意,由于杰克的评论指出并忽略了一些微妙之处,所以我接受的答案只是部分正确。
Shafik Yaghmour 2013年

就像FYI一样:在C(甚至C11)而不是C ++中,您不能标记声明。他们不在句法范畴内statement。在C ++中,您可以(语法类别的一个组成部分statementdeclaration statement)。
Jonathan Leffler

Answers:


195

{}表示的一个新的块范围

考虑以下非常人为的示例:

switch (a)
{
    case 42:
        int x = GetSomeValue();
        return a * x;
    case 1337:
        int x = GetSomeOtherValue(); //ERROR
        return a * x;
}

您将收到编译器错误,因为x已在范围中定义。

将它们分成自己的子作用域将消除x在switch语句之外进行声明的需要。

switch (a)
{
    case 42: {
        int x = GetSomeValue();
        return a * x; 
    }
    case 1337: {
        int x = GetSomeOtherValue(); //OK
        return a * x; 
    }
}

11
实际上,即使您跳过变量x的第二个声明,IMO也会出现编译器错误。
Abhishek Bansal

1
尽管过度使用了这种样式,并且在switch语句中放置了大块代码,但使它们难以理解。我宁愿保持简短的陈述。
masoud 2013年

2
@MatthieuM。对于一个事实,我知道MS Visual Studio 2010会具有Abhishek指示的行为:它将不会编译案例中的任何变量声明(除非您使用大括号来表示该案例中的新作用域)。我不知道这是否符合标准。
KRyan 2013年

1
@KRyan:并非如此,但是这是一种更加安全的选择,我几乎不能责怪他们执行此操作。
Matthieu M.

6
该标准的6.7(3)节(2005年草案的编号)指定您不能跳过初始化,因此不能在case块中进行初始化。
杰克·艾德利

23

TL; DR

可以在实例内部使用初始化器或一些非平凡的对象声明变量的唯一方法是使用或具有其自身范围(如循环if语句)的其他控制结构引入块范围{}

血腥细节

我们可以看到案例只是带有标签的语句,例如与goto语句一起使用的标签C ++草案标准第6.1节Labeled语句中对此进行了介绍),并且从第3节中可以看到,在许多情况下,不允许通过传递声明,包括带有初始化的代码:6.7

可以转移到块中,但不能以初始化绕过声明的方式进行。除非变量具有标量类型,具有琐碎的默认构造函数和琐碎的析构函数的程序,否则该程序会从不具有自动存储持续时间的变量不在范围内的点跳转到其处于范围内的点,格式为87,这些类型之一的cv限定版本,或者上述类型之一的数组,并且在没有初始化程序的情况下声明为(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 ;
}

是完全有效的(实时示例)。当然,如果您想在每种情况下都声明相同的变量,则它们各自将需要各自的作用域,但在switch语句之外它的作用方式也相同,因此这并不令人感到意外。

关于不允许跳过初始化的理由,缺陷报告467尽管涵盖了稍有不同的问题,但为自动变量提供了合理的理由:

自动变量,如果未显式初始化,则可能具有不确定的(“垃圾”)值,包括陷阱表示形式,[...]

看一下在多个情况下在一个切换中扩展范围的情况可能更有趣,最著名的例子可能是Duff的设备,它看起来像这样:

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
        }
}

6

这是一种习惯,使您可以将带有结果析构函数(或作用域冲突)的变量声明插入case子句中。另一种看待它的方式是,他们正在编写自己希望使用的语言,其中所有流控制均由块而不是语句序列组成。


4

检查这是基本的编译器限制,您将开始想知道发生了什么:

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";
}

1

如Rotem所说,在开关中使用括号表示一个新的范围。

但是在阅读时也可能为了清楚起见。要知道案件在哪里停止,因为您可能会有条件性的中断。


0

原因可能是:

  1. 可读性强,从视觉上增强了每种情况的作用域。
  2. 为多个开关案例声明具有相同名称的不同变量。
  3. 微优化-一个非常昂贵的资源分配变量的范围,您希望在离开案例范围后立即销毁该变量,甚至在使用“ GOTO”命令时遇到更复杂的情况。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.