Почему переменные не могут быть объявлены в инструкции switch?

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

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

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

инициализация «newVal» пропущена меткой «case»

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

23 Solutions collect form web for “Почему переменные не могут быть объявлены в инструкции switch?”

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

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

Этот вопрос одновременно помечен как [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: label и код станет действительным

     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.

ОК. Просто для того, чтобы прояснить это, это не имеет ничего общего с декларацией. Он относится только к «перепрыгиванию через инициализацию» (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 не стоит перескакивать по инициализации.

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

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

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

Обратите внимание на скобки.

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

 Case statements are only 'labels' 

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

§6.8.1 Маркированные заявления:

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

В 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 ++, согласно спецификации,

labeled-declaration разрешено, но помечено как -инициализация.

См. 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 () и инициализируйте ее разными значениями в случае, если она соответствует вашему требованию

     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 .

Вы не можете этого сделать, потому что ярлыки 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 аналогичен использованию goto , поэтому вам не разрешается перепрыгивать через локальную переменную с помощью конструктора.

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

 switch (...) { case FOO: { MyObject x(...); ... break; } ... } 

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

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

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

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

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

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

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

 switch (val) { case VAL: { int newVal = 42; } break; } 

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

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

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

Рассматривать:

 switch(val) { case VAL: int newVal = 42; default: int newVal = 23; } 

В отсутствие операторов break иногда newVal дважды объявляется, и вы не знаете, будет ли он работать до выполнения. Я предполагаю, что ограничение связано с этим путаницей. Каким будет объем newVal? Конвенция будет диктовать, что это будет весь блок переключателя (между фигурными скобками).

Я не программист на C ++, но в C:

 switch(val) { int x; case VAL: x=1; } 

Работает отлично. Объявление переменной внутри блока переключателя является прекрасным. Объявление после того, как защитник дела не является.

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

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

Если ваш код говорит «int newVal = 42», то вы разумно ожидаете, что newVal никогда не будет инициализирован. Но если вы переходите к этому утверждению (это то, что вы делаете), тогда это именно то, что происходит – newVal – в области видимости, но не назначено.

Если это то, что вы на самом деле должны были случиться, язык требует, чтобы он был явным, говоря «int newVal; newVal = 42;». В противном случае вы можете ограничить область действия newVal единственным случаем, что более вероятно, что вы хотели.

Это может прояснить ситуацию, если вы рассмотрите тот же пример, но с «const int newVal = 42;»

Я просто хотел подчеркнуть тонкий момент . Конструкция коммутатора создает целую область первого classа. Таким образом, можно объявить (и инициализировать) переменную в инструкции switch перед меткой первого случая без дополнительной пары скобок:

 switch (val) { /* This *will* work, even in C89 */ int newVal = 42; case VAL: newVal = 1984; break; case ANOTHER_VAL: newVal = 2001; break; } 

До сих пор ответы были для C ++.

Для C ++ вы не можете перепрыгнуть через инициализацию. Вы можете на C. Однако, в C, декларация не является выражением, а для ярлыков case должны следовать утверждения.

Итак, действительный (но уродливый) C, недопустимый C ++

 switch (something) { case 1:; // Ugly hack empty statement int i = 6; do_stuff_with_i(i); break; case 2: do_something(); break; default: get_a_life(); } 

Наоборот, в C ++ объявление является инструкцией, поэтому допустимо C ++, недействительный C

 switch (something) { case 1: do_something(); break; case 2: int i = 12; do_something_else(); } 

Интересно, что это нормально:

 switch (i) { case 0: int j; j = 7; break; case 1: break; } 

… но это не так:

 switch (i) { case 0: int j = 7; break; case 1: break; } 

Я понимаю, что исправление достаточно простое, но я еще не понимаю, почему первый пример не мешает компилятору. Как уже упоминалось ранее (на два года раньше хехе), декларация не является причиной ошибки, даже несмотря на логику. Инициализация – проблема. Если переменная инициализируется и объявляется в разных строках, она компилируется.

На этот вопрос я написал этот ответ. Однако, когда я закончил, я обнаружил, что ответ был закрыт. Поэтому я разместил его здесь, может быть, кому-то, кому нравятся ссылки на стандарт, будет полезно.

Исходный код:

 int i; i = 2; switch(i) { case 1: int k; break; case 2: k = 1; cout<  

На самом деле есть два вопроса:

1. Почему я могу объявить переменную после метки ярлыка?

Это потому, что в C ++ метка должна быть в форме:

N 3337 6,1 / 1

меченый-заявление:

...

  • attribute-specifier-seqopt case constant-expression : statement

...

И в заявлении о декларации C++ также рассматривается как оператор (в отличие от C ):

N 3337 6/1:

заявление :

...

заявление-заявление

...

2. Почему я могу перепрыгнуть через объявление переменной, а затем использовать его?

Потому что: N3337 6.7 / 3

Можно передать в блок, но не таким образом, чтобы обходить объявления с инициализацией . Программа, которая перескакивает ( перенос из условия оператора switch на метку case считается прыжком в этом отношении.)

от точки, где переменная с продолжительностью автоматического хранения не находится в области до точки, где она находится в области, плохо сформирована, если только переменная не имеет скалярного типа , типа classа с тривиальным конструктором по умолчанию и тривиальным деструктором, cv-квалифицированной версией одного из этих типов или массив одного из предыдущих типов и объявляется без инициализатора (8.5).

Так как k имеет скалярный тип и не инициализируется в точке объявления, то перескакивание над его объявлением возможно. Это семантически эквивалентно:

 goto label; int x; label: cout < < x << endl; 

Однако это было бы невозможно, если x был инициализирован в точке объявления:

  goto label; int x = 58; //error, jumping over declaration with initialization label: cout < < x << endl; 

Новые переменные могут быть декалированы только при объеме блока. Вам нужно написать что-то вроде этого:

 case VAL: // This will work { int newVal = 42; } break; 

Конечно, newVal имеет только frameworks в фигурных скобках …

Привет, Ральф

Блок- switch не совпадает с последовательностью if/else if blocks. Я удивлен, что ни один другой ответ не объясняет это четко.

Рассмотрим этот оператор switch :

 switch (value) { case 1: int a = 10; break; case 2: int a = 20; break; } 

Это может быть удивительно, но компилятор не увидит это как простой if/else if . Он выдает следующий код:

 if (value == 1) goto label_1; else if (value == 2) goto label_2; else goto label_end; { label_1: int a = 10; goto label_end; label_2: int a = 20; // Already declared ! goto label_end; } label_end: // The code after the switch block 

Операторы case преобразуются в метки, а затем goto с goto . Скобки создают новую область, и теперь легко понять, почему вы не можете объявить две переменные с тем же именем в блоке switch .

Это может показаться странным, но необходимо поддерживать провал (то есть не использовать break чтобы исполнение продолжалось до следующего case ).

newVal существует во всей области действия переключателя, но только инициализируется, если поражена конечная часть VAL. Если вы создадите блок вокруг кода в VAL, это должно быть ОК.

Стандарт C ++ имеет: Можно передать в блок, но не таким образом, чтобы обходить объявления с инициализацией. Программа, которая перескакивает с точки, где локальная переменная с продолжительностью автоматического хранения не находится в области до точки, где она находится в области видимости, плохо сформирована, если переменная не имеет тип POD (3.9) и объявлена ​​без инициализатора (8.5).

Код для иллюстрации этого правила:

 #include  using namespace std; class X { public: X() { cout < < "constructor" << endl; } ~X() { cout << "destructor" << endl; } }; template  void ill_formed() { goto lx; ly: type a; lx: goto ly; } template  void ok() { ly: type a; lx: goto ly; } void test_class() { ok(); // compile error ill_formed(); } void test_scalar() { ok(); ill_formed(); } int main(int argc, const char *argv[]) { return 0; } 

Код для отображения эффекта инициализации:

 #include  using namespace std; int test1() { int i = 0; // There jumps fo "case 1" and "case 2" switch(i) { case 1: // Compile error because of the initializer int r = 1; break; case 2: break; }; } void test2() { int i = 2; switch(i) { case 1: int r; r= 1; break; case 2: cout < < "r: " << r << endl; break; }; } int main(int argc, const char *argv[]) { test1(); test2(); return 0; } 

Я считаю, что проблема в том, что это утверждение было пропущено, и вы пытались использовать var в другом месте, но не объявлялись.

Похоже, анонимные объекты могут быть объявлены или созданы в операторе case switch по той причине, что они не могут быть указаны и как таковые не могут перейти к следующему случаю. Рассмотрим, что этот пример компилируется в GCC 4.5.3 и Visual Studio 2008 (может быть, проблема соответствия требованиям, поэтому эксперты взвешивают)

 #include  struct Foo{}; int main() { int i = 42; switch( i ) { case 42: Foo(); // Apparently valid break; default: break; } return EXIT_SUCCESS; } 
Давайте будем гением компьютера.