Я всегда задавался этим вопросом - почему вы не можете объявлять переменные после метки case в инструкции switch? В С++ вы можете объявить переменные почти везде (и объявить их близкими к первому использованию, очевидно, хорошо), но следующее все равно не будет работать:
switch (val)
{
case VAL:
// This won't work
int newVal = 42;
break;
case ANOTHER_VAL:
...
break;
}
Вышеуказанная ситуация дает мне следующую ошибку (MSC):
инициализация "newVal" пропускается меткой "case"
Это тоже ограничение на других языках. Почему такая проблема?
Case
- это только метки. Это означает, что компилятор интерпретирует это как переход непосредственно к метке. В С++ проблема здесь - одна из областей. Ваши фигурные скобки определяют область как все внутри оператора switch
. Это означает, что вы остаетесь с областью действия, где скачок будет выполняться далее в коде, пропуская инициализацию. Правильный способ справиться с этим - определить область, специфичную для этого оператора Case
, и определить в нем свою переменную.
switch (val)
{
case VAL:
{
// This will work
int newVal = 42;
break;
}
case ANOTHER_VAL:
...
break;
}
Этот вопрос отмечен одновременно как [C] и [С++]. Исходный код действительно недействителен как для C, так и для С++, но для совершенно разных причин. Я считаю, что эта важная деталь была упущена (или запутана) существующими ответами.
В С++ этот код недействителен, поскольку метка case ANOTHER_VAL:
переходит в область переменной newVal
, минуя ее инициализацию. Переходы, которые обходят инициализацию локальных объектов, являются незаконными в С++. Эта сторона вопроса правильно решена большинством ответов.
Однако в языке 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, хотя эти проблемы бывают очень разными. На стороне С++ он ограничивает область newVal
, убедившись, что case ANOTHER_VAL:
больше не переходит в эту область, что устраняет проблему С++. На стороне 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, он остается недопустимым с точки зрения С++.
Симметрично, в случае С++ проблему можно легко решить без {}
. Просто удалите инициализатор из объявления переменной, и код станет действительным.
switch (val)
{
case VAL:
int newVal;
newVal = 42;
break;
case ANOTHER_VAL: /* Now it works in C++! */
...
break;
}
Обратите внимание, что, хотя он теперь действителен с точки зрения С++, он остается недействительным с точки зрения C.
newVal
при ANOTHER_VAL
к ANOTHER_VAL
?
case ANOTHER_VAL:
переменная точки newVal
видна, но с неопределенным значением.
Ok. Просто для того, чтобы прояснить это, это не имеет ничего общего с декларацией. Он относится только к "перепрыгиванию через инициализацию" (ISO С++ '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 это не ошибка перехода по инициализации.
Как уже упоминалось, решение состоит в том, чтобы добавить вложенный блок, чтобы время жизни переменной ограничивалось отдельной меткой 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. Здесь я также получаю ту же ошибку.
В С++, согласно спецификации,
labeled-declaration разрешено, но помечено как -инициализация.
См. 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() и инициализировать ее различными значениями в случае, если она соответствует вашему требованию
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;
}
a
в область видимости переменной a
. Итак, с точки зрения C, проблема в case VAL:
label, и вы правильно его описали. Но с точки зрения C ++ проблема заключается в case ANOTHER_VAL:
label.
Вы не можете этого сделать, потому что метки case
фактически являются точками входа в содержащий блок.
Это наиболее ярко иллюстрируется устройством Duff. Вот какой код из Википедии:
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;
}
...
}
SAR
в x86, по сравнению с SHR
для беззнаковых смен).
Большинство ответов пока неверны в одном отношении: вы можете объявлять переменные после оператора 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:
switch(val) {
int x;
case VAL:
x=1;
}
Прекрасно работает. Объявление переменной внутри блока переключателя является прекрасным. Объявление после того, как охранник не будет.
Весь раздел коммутатора представляет собой единый контекст декларации. Вы не можете объявить переменную в таком случае. Вместо этого попробуйте:
switch (val)
{
case VAL:
{
// This will work
int newVal = 42;
break;
}
case ANOTHER_VAL:
...
break;
}
Я написал этот ответ в основном для этого вопроса. Однако, когда я закончил, я обнаружил, что ответ был закрыт. Поэтому я разместил его здесь, может быть, кому-то, кому нравятся ссылки на стандарт, будет полезно.
Оригинальный код:
int i;
i = 2;
switch(i)
{
case 1:
int k;
break;
case 2:
k = 1;
cout<<k<<endl;
break;
}
На самом деле есть 2 вопроса:
1. Почему я могу объявить переменную после метки case
?
Это потому, что в С++ метка должна быть в форме:
N3337 6.1/1
меченый-оператор:
...
- attribute-specifier-seqopt
case
constant-expression
:statement
...
И в заявлении объявления C++
также рассматривается как оператор (в отличие от C
):
N3337 6/1:
утверждение:
...
декларация-выражение
...
2. Почему я могу перейти через объявление переменной, а затем использовать его?
Потому что: N3337 6.7/3
Можно передать в блок , но не таким образом, чтобы обходить объявления с инициализацией. программа, которая прыгает ( передача из условия оператора оператора на ярлык case рассматривается как прыжок в этом отношении.)
из точки, где переменная с продолжительностью автоматического хранения не находится в области до точки, где она находится в области, неформатирована , если переменная не имеет скалярный тип тип класса с тривиальным значением по умолчанию конструктор и тривиальный деструктор, 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;
Интересно, что это нормально:
switch (i)
{
case 0:
int j;
j = 7;
break;
case 1:
break;
}
... но это не так:
switch (i)
{
case 0:
int j = 7;
break;
case 1:
break;
}
Я понимаю, что исправление достаточно простое, но я еще не понимаю, почему первый пример не мешает компилятору. Как уже упоминалось ранее (на два года раньше хехе), декларация не вызывает ошибок, даже несмотря на логику. Инициализация - проблема. Если переменная инициализируется и объявляется в разных строках, она компилируется.
До сих пор ответы были для С++.
Для С++ вы не можете перепрыгнуть через инициализацию. Вы можете в C. Однако, в C, объявление не является выражением, а для ярлыков case должны следовать утверждения.
Итак, действительный (но уродливый) 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
switch (something)
{
case 1:
do_something();
break;
case 2:
int i = 12;
do_something_else();
}
Если ваш код говорит "int newVal = 42", вы бы разумно ожидали, что newVal никогда не будет инициализирован. Но если вы перейдете к этому утверждению (это то, что вы делаете), то это именно то, что происходит - newVal - в области видимости, но не назначено.
Если это то, что вы на самом деле должны были случиться, тогда для этого языка необходимо сделать его явным, указав "int newVal; newVal = 42;". В противном случае вы можете ограничить область действия newVal единственным случаем, что более вероятно, что вы хотели.
Это может прояснить ситуацию, если вы рассмотрите тот же пример, но с "const int newVal = 42;"
Я просто хотел подчеркнуть тонкий пункт. Конструкция коммутатора создает целую область первого класса. Таким образом, можно объявить (и инициализировать) переменную в инструкции switch перед меткой первого случая без дополнительной пары кронштейнов:
switch (val) {
/* This *will* work, even in C89 */
int newVal = 42;
case VAL:
newVal = 1984;
break;
case ANOTHER_VAL:
newVal = 2001;
break;
}
int newVal
, но не присвоение = 42
.
A switch
блок не совпадает с последовательностью блоков if/else if
. Я удивлен, что ни один другой ответ не объясняет это четко.
Рассмотрим этот оператор 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
. Скобки создают новую область видимости, и теперь легко понять, почему вы не можете объявить две переменные с тем же именем в блоке switch
.
Это может показаться странным, но необходимо поддерживать провал (т.е. не использовать break
, чтобы выполнение продолжалось до следующего case
).
Новые переменные могут быть декалированы только в области блока. Вам нужно написать что-то вроде этого:
case VAL:
// This will work
{
int newVal = 42;
}
break;
Конечно, newVal имеет только рамки в фигурных скобках...
Приветствия, Ральф
Похоже, анонимные объекты могут быть объявлены или созданы в операторе case switch по той причине, что они не могут быть указаны и как таковые не могут пройти до следующего случая. Рассмотрим этот пример, который компилируется в GCC 4.5.3 и Visual Studio 2008 (может быть, проблема соответствия требованиям, поэтому эксперты взвешивают)
#include <cstdlib>
struct Foo{};
int main()
{
int i = 42;
switch( i )
{
case 42:
Foo(); // Apparently valid
break;
default:
break;
}
return EXIT_SUCCESS;
}
const
ссылкой с собственной областью видимости). Это выражение, которое живет и умирает в своем утверждении (где бы оно ни было). Поэтому это совершенно не имеет значения.
Стандарт С++ имеет: Можно передать в блок, но не таким образом, чтобы обходить объявления с инициализацией. Программа, которая перескакивает с точки, где локальная переменная с продолжительностью автоматического хранения не находится в области до точки, где она находится в области видимости, плохо сформирована, если переменная не имеет тип POD (3.9) и объявлена без инициализатора (8.5).
Код для иллюстрации этого правила:
#include <iostream>
using namespace std;
class X {
public:
X()
{
cout << "constructor" << endl;
}
~X()
{
cout << "destructor" << endl;
}
};
template <class type>
void ill_formed()
{
goto lx;
ly:
type a;
lx:
goto ly;
}
template <class type>
void ok()
{
ly:
type a;
lx:
goto ly;
}
void test_class()
{
ok<X>();
// compile error
ill_formed<X>();
}
void test_scalar()
{
ok<int>();
ill_formed<int>();
}
int main(int argc, const char *argv[])
{
return 0;
}
Код для отображения эффекта инициализации:
#include <iostream>
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;
}
newVal существует во всей области действия коммутатора, но только инициализируется, если поражена конечная часть VAL. Если вы создаете блок вокруг кода в VAL, он должен быть в порядке.
Я считаю, что проблема в том, что это утверждение было пропущено, и вы пытались использовать var в другом месте, он не будет объявлен.