11

Почему переменные нельзя объявлять в операторе switch?

14

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

switch (val)  
{  
case VAL:  
  // Это не сработает
  int newVal = 42;  
  break;
case ANOTHER_VAL:  
  ...
  break;
}  

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

initialization of 'newVal' is skipped by 'case' label

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

5 ответ(ов)

13

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

Правильный способ решения этой проблемы — определить область видимости, специфичную для данного case оператора, и объявить вашу переменную внутри этих скобок:

switch (val)
{   
case VAL:  
{
  // Это будет работать
  int newVal = 42;  
  break;
}
case ANOTHER_VAL:  
...
break;
}
1

Чтобы прояснить, это строго не связано с объявлением переменной. Речь идет исключительно о "прыжке через инициализацию" (ISO C++ '03 6.7/3).

Многочисленные сообщения здесь упоминают, что прыжок через объявление может привести к тому, что переменная "не будет объявлена". Это не так. Объект POD (Plain Old Data) может быть объявлен без инициализатора, но он будет иметь неопределенное значение. Например:

switch (i)
{
   case 0:
     int j; // 'j' имеет неопределенное значение
     j = 0; // 'j' установлен (не инициализирован) в 0, но это выражение
            // пропускается, когда 'i == 1'
     break;
   case 1:
     ++j;   // 'j' находится в области видимости здесь – но у него неопределенное значение
     break;
}

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

class A {
public:
  A ();
};

switch (i) // Ошибка - прыжок через инициализацию 'A'
{
   case 0:
     A j;   // Компилятор неявно вызывает конструктор по умолчанию
     break;
   case 1:
     break;
}

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

goto LABEL;    // Ошибка - прыжок через инициализацию
int j = 0; 
LABEL:
  ;

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

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

0

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

switch (val)
{
    case VAL:
    {
        // Это **будет** работать
        int newVal = 42;
    }
    break;

    case ANOTHER_VAL:
        ...
    break;
}

Обратите внимание на фигурные скобки.

0

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

Операторы case — это лишь 'метки'

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

§6.8.1 Помеченные операторы:

помеченный-оператор:
    идентификатор : оператор
    case константное-выражение : оператор
    default : оператор

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

Поэтому следующий код:

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;   // Объявляем перед
        switch(a)
        {
        case 1: x=10;
            break;
    
        case 2: x=20;
            break;
        }
    }
    

Некоторые дополнительные заметки о операторе switch

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

switch(a)
{
    printf("Это никогда не будет выведено"); // Это никогда не будет выполнено

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

    default:
        break;
}

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

0

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

case 1:
    int x; // Работает
    int y = 0; // Ошибка, инициализация пропускается для case
    break;
case 2:
    ...

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

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь