Почему переменные не могут быть объявлены в операторе switch? - PullRequest
873 голосов
/ 18 сентября 2008

Меня всегда удивляло это - почему вы не можете объявить переменные после метки регистра в операторе switch? В C ++ вы можете объявлять переменные практически везде (и объявление их близко к первому использованию, очевидно, хорошо), но следующее все равно не будет работать:

switch (val)  
{  
case VAL:  
  // This won't work
  int newVal = 42;  
  break;
case ANOTHER_VAL:  
  ...
  break;
}  

Вышеприведенное дает мне следующую ошибку (MSC):

инициализация 'newVal' пропускается меткой 'case'

Это кажется ограничением и в других языках. Почему это такая проблема?

Ответы [ 23 ]

1064 голосов
/ 18 сентября 2008

Case заявления только метки . Это означает, что компилятор интерпретирует это как переход непосредственно к метке. В C ++ проблема здесь одна из области видимости. Ваши фигурные скобки определяют область видимости как все внутри оператора switch. Это означает, что вы остаетесь с областью, в которой будет выполняться переход к коду, пропускающему инициализацию. Правильный способ справиться с этим - определить область действия, специфичную для этого оператора case, и определить в нем переменную.

switch (val)
{   
case VAL:  
{
  // This will work
  int newVal = 42;  
  break;
}
case ANOTHER_VAL:  
...
break;
}
295 голосов
/ 07 ноября 2013

Этот вопрос is был изначально помечен как [C] и [C ++] одновременно. Исходный код действительно недействителен как на C, так и на C ++, но по совершенно другим не связанным причинам. Я полагаю, что эти важные детали были упущены (или запутаны) существующими ответами.

  • В C ++ этот код недопустим, поскольку метка case ANOTHER_VAL: попадает в область действия переменной newVal, минуя ее инициализацию. Переходы, которые обходят инициализацию локальных объектов, недопустимы в C ++. Эта сторона вопроса правильно решается большинством ответов.

  • Однако в языке C обход инициализации переменной не является ошибкой. Прыжок в область действия переменной при ее инициализации допустим в C. Это просто означает, что переменная остается неинициализированной. Оригинальный код не компилируется в C по совершенно другой причине. Метка case VAL: в исходном коде прилагается к объявлению переменной newVal. В языке C декларации не являются заявлениями. Они не могут быть помечены. И это то, что вызывает ошибку, когда этот код интерпретируется как код C.

    switch (val)  
    {  
    case VAL:             /* <- C error is here */
      int newVal = 42;  
      break;
    case ANOTHER_VAL:     /* <- C++ error is here */
      ...
      break;
    }
    

Добавление дополнительного блока {} устраняет проблемы как C ++, так и C, даже если эти проблемы очень разные. На стороне C ++ это ограничивает область действия newVal, гарантируя, что case ANOTHER_VAL: больше не переходит в эту область, что устраняет проблему C ++. На стороне C этот дополнительный {} вводит составной оператор, тем самым делая метку case VAL: для применения к оператору, что устраняет проблему C.

  • В случае C проблема может быть легко решена без {}. Просто добавьте пустой оператор после метки case VAL:, и код станет действительным

    switch (val)  
    {  
    case VAL:;            /* Now it works in C! */
      int newVal = 42;  
      break;
    case ANOTHER_VAL:  
      ...
      break;
    }
    

    Обратите внимание: несмотря на то, что теперь он действителен с точки зрения C, он остается недействительным с точки зрения C ++.

  • Симметрично, в случае C ++ проблема может быть легко решена без {}. Просто удалите инициализатор из объявления переменной, и код станет действительным

    switch (val)  
    {  
    case VAL: 
      int newVal;
      newVal = 42;  
      break;
    case ANOTHER_VAL:     /* Now it works in C++! */
      ...
      break;
    }
    

    Обратите внимание: несмотря на то, что теперь он действителен с точки зрения C ++, он остается недействительным с точки зрения C.

131 голосов
/ 18 сентября 2008

Хорошо. Просто уточнить это строго не имеет ничего общего с декларацией. Это относится только к «перепрыгиванию через инициализацию» (ISO C ++ '03 6.7 / 3)

Во многих постах здесь упоминается, что перепрыгивание через объявление может привести к тому, что переменная "не будет объявлена". Это неправда. Объект POD может быть объявлен без инициализатора, но он будет иметь неопределенное значение. Например:

switch (i)
{
   case 0:
     int j; // 'j' has indeterminate value
     j = 0; // 'j' initialized to 0, but this statement
            // is jumped when 'i == 1'
     break;
   case 1:
     ++j;   // 'j' is in scope here - but it has an indeterminate value
     break;
}

Если объект не POD или агрегат, компилятор неявно добавляет инициализатор, и поэтому невозможно перепрыгнуть через такое объявление:

class A {
public:
  A ();
};

switch (i)  // Error - jumping over initialization of 'A'
{
   case 0:
     A j;   // Compiler implicitly calls default constructor
     break;
   case 1:
     break;
}

Это ограничение не ограничено оператором switch. Также ошибочно использовать 'goto', чтобы перепрыгнуть через инициализацию:

goto LABEL;    // Error jumping over initialization
int j = 0; 
LABEL:
  ;

Немного мелочей в том, что это разница между C ++ и C. В C не является ошибкой перепрыгивать через инициализацию.

Как уже упоминалось, решение состоит в том, чтобы добавить вложенный блок так, чтобы время жизни переменной ограничивалось индивидуальной меткой регистра.

35 голосов
/ 18 сентября 2008

Весь оператор switch находится в той же области. Чтобы обойти это, сделайте это:

switch (val)
{
    case VAL:
    {
        // This **will** work
        int newVal = 42;
    }
    break;

    case ANOTHER_VAL:
      ...
    break;
}

Примечание скобки.

28 голосов
/ 18 декабря 2011

После прочтения всех ответов и дополнительных исследований я получаю несколько вещей.

Case statements are only 'labels'

В С, в соответствии со спецификацией,

§6.8.1 Помеченные заявления:

labeled-statement:
    identifier : statement
    case constant-expression : statement
    default : statement

В C нет ни одного предложения, которое допускает «помеченное объявление». Это просто не часть языка.

So

case 1: int x=10;
        printf(" x is %d",x);
break;

Этот не будет компилироваться , см. http://codepad.org/YiyLQTYw. GCC выдает ошибку:

label can only be a part of statement and declaration is not a statement

Даже

  case 1: int x;
          x=10;
            printf(" x is %d",x);
    break;

это также не компилируется , см. http://codepad.org/BXnRD3bu. Здесь я также получаю ту же ошибку.


В C ++, согласно спецификации,

помечено-объявление разрешено, но помечено -инициализация не допускается.

См. http://codepad.org/ZmQ0IyDG.


Решение для такого условия два

  1. Либо используйте новую область видимости, используя {}

    case 1:
           {
               int x=10;
               printf(" x is %d", x);
           }
    break;
    
  2. Или используйте фиктивное утверждение с меткой

    case 1: ;
               int x=10;
               printf(" x is %d",x);
    break;
    
  3. Объявите переменную перед switch () и инициализируйте ее различными значениями в выражении case, если она удовлетворяет вашему требованию

    main()
    {
        int x;   // Declare before
        switch(a)
        {
        case 1: x=10;
            break;
    
        case 2: x=20;
            break;
        }
    }
    

Еще несколько вещей с оператором switch

Никогда не записывайте в коммутатор какие-либо операторы, которые не являются частью какой-либо метки, поскольку они никогда не будут выполнены:

switch(a)
{
    printf("This will never print"); // This will never executed

    case 1:
        printf(" 1");
        break;

    default:
        break;
}

См. http://codepad.org/PA1quYX3.

20 голосов
/ 18 сентября 2008

Вы не можете сделать это, потому что case метки на самом деле являются просто точками входа в содержащий блок.

Это наиболее четко показано на устройстве Даффа . Вот некоторый код из Википедии:

strcpy(char *to, char *from, size_t count) {
    int n = (count + 7) / 8;
    switch (count % 8) {
    case 0: do { *to = *from++;
    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);
    }
}

Обратите внимание, что метки case полностью игнорируют границы блоков. Да, это зло. Но именно поэтому ваш пример кода не работает. Переход к метке case аналогичен использованию goto, поэтому вы не можете перепрыгивать через локальную переменную с помощью конструктора.

Как указали несколько других плакатов, вам нужно поставить свой блок:

switch (...) {
    case FOO: {
        MyObject x(...);
        ...
        break; 
    }
    ...
 }
16 голосов
/ 18 сентября 2008

Большинство ответов пока неверны в одном отношении: вы можете объявлять переменные после оператора case, но вы не можете инициализировать их:

case 1:
    int x; // Works
    int y = 0; // Error, initialization is skipped by case
    break;
case 2:
    ...

Как упоминалось ранее, хорошим способом решения этой проблемы является использование фигурных скобок для создания области действия для вашего дела.

12 голосов
/ 18 сентября 2008

Мой любимый трюк со злым переключателем - использовать if (0) для пропуска нежелательной метки регистра.

switch(val)
{
case 0:
// Do something
if (0) {
case 1:
// Do something else
}
case 2:
// Do something in all cases
}

Но очень злой.

10 голосов
/ 18 сентября 2008

Попробуйте это:

switch (val)
{
    case VAL:
    {
        int newVal = 42;
    }
    break;
}
7 голосов
/ 18 сентября 2008

Вы можете объявить переменные в операторе switch , если вы начнете новый блок:

switch (thing)
{ 
  case A:
  {
    int i = 0;  // Completely legal
  }
  break;
}

Причина заключается в выделении (и освобождении) пространства в стеке для хранения локальной переменной (переменных).

...