Почему переменные нельзя объявлять в операторе switch?
У меня всегда возникал вопрос: почему нельзя объявлять переменные после метки 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 ответ(ов)
case
операторы являются лишь метками. Это означает, что компилятор интерпретирует это как переход непосредственно к метке. В C++ здесь возникает проблема с областью видимости. Ваши фигурные скобки определяют область видимости как всё, что находится внутри оператора switch
. Это означает, что вы оставляете область видимости, в которой переход будет выполнен дальше в коде, пропуская инициализацию.
Правильный способ решения этой проблемы — определить область видимости, специфичную для данного case
оператора, и объявить вашу переменную внутри этих скобок:
switch (val)
{
case VAL:
{
// Это будет работать
int newVal = 42;
break;
}
case ANOTHER_VAL:
...
break;
}
Чтобы прояснить, это строго не связано с объявлением переменной. Речь идет исключительно о "прыжке через инициализацию" (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.
Вся конструкция switch
находится в одном и том же скоупе. Чтобы обойти это ограничение, сделайте следующее:
switch (val)
{
case VAL:
{
// Это **будет** работать
int newVal = 42;
}
break;
case ANOTHER_VAL:
...
break;
}
Обратите внимание на фигурные скобки.
После прочтения всех ответов и дополнительных исследований я пришел к нескольким выводам.
Операторы 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.
Решить такую ситуацию можно двумя способами:
Либо использовать новую область видимости с помощью
case 1: { int x=10; printf(" x is %d", x); } break;
Либо использовать пустое выражение с меткой
case 1: ; int x=10; printf(" x is %d",x); break;
Объявить переменную перед конструкцией 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;
}
Большинство ответов до сих пор неправы в одном отношении: вы можете объявлять переменные после оператора case
, но вы не можете их инициализировать:
case 1:
int x; // Работает
int y = 0; // Ошибка, инициализация пропускается для case
break;
case 2:
...
Как было упомянуто ранее, неплохой обходной путь — использовать фигурные скобки для создания области видимости внутри вашего case.
В чем разница между #include <filename> и #include "filename"?
Как изменить цвет вывода echo в Linux
Каково влияние extern "C" в C++?
Разница между const int*, const int * const и int * const?
Является ли < быстрее, чем <=?