Я всегда задавался вопросом - почему вы не можете объявить переменные после метки case в операторе switch? В C++ вы можете объявлять переменные практически где угодно (и объявление их близко к первому использованию, очевидно, хорошо), но следующее по-прежнему не сработает:
switch (val)
{
case VAL:
// This won't work
int newVal = 42;
break;
case ANOTHER_VAL:
...
break;
}
Вышеупомянутое дает мне следующую ошибку (MSC):
initialization of 'newVal' is skipped by 'case' label
Это кажется ограничением и для других языков. Почему это такая проблема?
Я бы сказал: «Почему переменные нельзя инициализировать в операторе switch, а не объявлять?» Поскольку простое объявление переменной дает мне только предупреждение в MSVC.
Если вы поместите все внутри метки case в фигурные скобки {}, то все заработает.





Весь оператор switch находится в той же области. Чтобы обойти это, сделайте следующее:
switch (val)
{
case VAL:
{
// This **will** work
int newVal = 42;
}
break;
case ANOTHER_VAL:
...
break;
}
Примечание скобки.
Попробуй это:
switch (val)
{
case VAL:
{
int newVal = 42;
}
break;
}
Я считаю, что проблема заключается в том, что оператор был пропущен, и вы пытались использовать var в другом месте, он не был объявлен.
Вы можете объявлять переменные внутри оператора switch если, вы начинаете новый блок:
switch (thing)
{
case A:
{
int i = 0; // Completely legal
}
break;
}
Причина в том, чтобы выделить (и освободить) пространство в стеке для хранения локальных переменных.
Переменная может быть объявлена, но не может быть инициализирована. Кроме того, я почти уверен, что проблема никак не связана со стеком и локальными переменными.
Вы не можете этого сделать, потому что метки 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;
}
...
}
В этой реализации устройства Даффа есть ошибка, которая делает его чрезвычайно медленным: count имеет тип int, поэтому% должен выполнять настоящую операцию деления / по модулю. Сделайте count без знака (или еще лучше, всегда используйте size_t для счетчиков / индексов), и проблема исчезнет.
@R ..: Что ?! В системе с дополнением до двух подписи не влияют на модули по степеням 2 (это просто AND для нижних битов) и не влияет на деление по степеням 2, пока в вашей архитектуре процессора есть арифметическая операция сдвига вправо. (SAR в x86, по сравнению с SHR, который предназначен для беззнаковых сдвигов).
@Chris: Я полагаю, он имеет в виду, когда компилятор должен разрешать отрицательные значения, когда «просто И для нижних битов» не выполняется; например, -1% 8 дает -1 в этой системе дополнения до двух с использованием g ++ (знак в этом случае - реализация, определенная в 5.6 / 4).
@Roger: Да, компилятор должен учитывать отрицательные значения и лишние циклы (так что беззнаковый еще более идеален), но на x86 (с которым я тестировал) gcc по-прежнему не генерирует инструкцию DIV. Это эквивалент: num < 0 ? ((num + 7) & 7) - 7 : num & 7 (но без каких-либо ветвлений, умножения или деления вообще). Так что это ни в коем случае не "очень медленно".
Что ж, это все еще значительно медленнее, чем просто побитовое и. На современных процессорах я не удивлюсь, если этот код будет почти таким же медленным, как разделение. Моя точка зрения остается неизменной: вы должны использовать беззнаковые типы или писать побитовые и сами.
@Roger: знак больше не определяется реализацией. C99 определяет деление с отрицательными числами и определяет его неверно, так что теперь это неприятное поведение требуется стандартом. :-(
@Chris: Я согласен с вами, что R преувеличивает влияние; Я видел только ваш комментарий и знал, что простого И недостаточно.
@R ..: Исходный код в этом комментарии был взят из Википедии, как указано выше. Я только что исправил это, чтобы использовать size_t. Спасибо, что указали на проблемы с оператором% C99; об этом стоит помнить.
Также стоит отметить, что исходный код Википедии предназначен для отправки данных в вывод с отображением памяти, что здесь выглядит странно, потому что он не упоминается, и каждый байт копируется в одно и то же место «в». Можно обойти это, либо добавив postfix ++ в to, либо упомянув вариант использования для ввода-вывода с отображением памяти. Совершенно второстепенный по отношению к исходному вопросу :-).
newVal существует во всей области действия переключателя, но инициализируется только при попадании в конечность VAL. Если вы создаете блок вокруг кода в VAL, все должно быть в порядке.
Весь раздел переключателя представляет собой единый контекст объявления. Вы не можете объявить переменную в таком операторе case. Попробуйте вместо этого:
switch (val)
{
case VAL:
{
// This will work
int newVal = 42;
break;
}
case ANOTHER_VAL:
...
break;
}
Переменная может быть объявлена, но не может быть инициализирована.
@Richard Corden Я уверен, что инициализация сработает. Вы все еще утверждаете, что его нельзя инициализировать?
Операторы Case - это только этикетки. Это означает, что компилятор интерпретирует это как переход непосредственно к метке. В C++ проблема заключается в области видимости. Фигурные скобки определяют область как все, что находится внутри оператора switch. Это означает, что у вас остается область, в которой будет выполняться переход в код, пропуская инициализацию.
Правильный способ справиться с этим - определить область, специфичную для этого оператора case, и определить в ней свою переменную:
switch (val)
{
case VAL:
{
// This will work
int newVal = 42;
break;
}
case ANOTHER_VAL:
...
break;
}
По сравнению с открытием новой области видимости - предпочтение читаемости и согласованности кода. Раньше вы могли автоматически получать «лишний» фрейм стека, но теперь этого не должно быть ни в одном приличном оптимизирующем компиляторе.
Я согласен с Джеффом - слишком легко «предположить» область видимости при чтении оператора switch из-за стиля отступов, который использует большинство людей. Мой собственный стиль - всегда открывать новую область видимости для каждого случая / по умолчанию, если она длиннее одной строки.
workmad3 - Сможете ли вы найти мне вообще какой-нибудь компилятор C++, который сгенерирует новый фрейм стека, если вы не объявите никаких новых переменных? Вы меня вкратце обеспокоили, но ни один из G ++ 3.1, Visual C++ 7 или Intel C++ 8 не будет генерировать какой-либо код для новых областей, в которых вы не объявляете никаких переменных.
Может ли кто-нибудь помочь мне с тем, как создается еще один фрейм стека, просто добавляя скобки?
@ workmad3 путем ввода нового блока фигурных скобок не вызывает новый кадр стека stackoverflow.com/questions/2759371/…
Проверить, был ли создан фрейм стека, легко - вы увидите новую строку в окне стека вызовов и не увидите значений локальных переменных внешнего блока в отладчике, пока вы не переключитесь на «основной» фрейм из функция. Я никогда не видел такого компилятора, даже Visual Studio 6.0, выпущенная в 1998 году, этого не делает.
@TallJef Я не знаю, о каких «старых временах» вы говорите. Я никогда не встречал компилятора, где все пространство стека для метода не выделялось при вводе метода, за 40 лет.
@EJP: Ну, когда используется _alloca(), компилятор не может знать, сколько места требуется при входе, поэтому ему приходится вносить частичные корректировки.
В компиляторе IAR я столкнулся со своеобразной ситуацией с подобным государственным менетом. Внутри case был массив (с областью видимости), но память выделялась независимо от ввода case, просто путем ввода функции. Поскольку другие случаи привели к более глубокому стеку, чем этот, в конечном итоге это привело к переполнению стека.
@MarquisofLorne У меня определенно есть. Фактически полагался на это в каком-то приложении, где у меня была рекурсивная функция с временным массивом, который не был выделен через весь вызов функции, а не при выполнении рекурсивного вызова.
Если в вашем коде написано «int newVal = 42», то можно разумно ожидать, что newVal никогда не будет неинициализирован. Но если вы перейдете к этому оператору (что вы и делаете), то именно это и произойдет - newVal входит в область видимости, но не был назначен.
Если это именно то, что вы действительно хотели, тогда язык требует сделать это явным, сказав «int newVal; newVal = 42;». В противном случае вы можете ограничить область действия newVal одним случаем, что более вероятно, что вы хотели.
Это может прояснить ситуацию, если вы рассмотрите тот же пример, но с "const int newVal = 42;"
Учитывать:
switch(val)
{
case VAL:
int newVal = 42;
default:
int newVal = 23;
}
При отсутствии операторов break иногда newVal объявляется дважды, и вы не знаете, будет ли это до времени выполнения. Я предполагаю, что ограничение вызвано такой путаницей. Каковы будут возможности newVal? Согласно соглашению, это будет весь блок переключателя (между скобками).
Я не программист на C++, но на C:
switch(val) {
int x;
case VAL:
x=1;
}
Работает отлично. Объявление переменной внутри блока переключателя - это нормально. Заявления после того, как охранник дела нет.
@ Mr.32: на самом деле ваш пример показывает, что printf не выполняется, но в этом случае int x не является оператором, а объявлением, x объявляется, пространство для него резервируется каждый раз, когда среда функции складывается, см .: codepad.org/4E9Zuz1e
Я ожидал найти это при чтении заголовка вопроса, потому что вопрос не в объявлении переменных в метках «case:», а в операторах switch. И только вы (и VictorH, подчеркивая свой ответ) на самом деле говорили о переменных в операторах switch.
Новые переменные можно объявлять только в области видимости блока. Вам нужно написать примерно так:
case VAL:
// This will work
{
int newVal = 42;
}
break;
Конечно, newVal имеет только объем в фигурных скобках ...
Привет, Ральф
Ok. Просто уточнить это строго не имеет отношения к декларации. Относится только к «перепрыгиванию инициализации» (ISO C++ '03 6.7 / 3)
Во многих сообщениях здесь упоминается, что переход через объявление может привести к тому, что переменная «не будет объявлена». Это неправда. Объект POD может быть объявлен без инициализатора, но он будет иметь неопределенное значение. Например:
switch (i)
{
case 0:
int j; // 'j' has indeterminate value
j = 0; // 'j' set (not 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 перепрыгивать через инициализацию не является ошибкой.
Как уже упоминалось, решение состоит в том, чтобы добавить вложенный блок, чтобы время жизни переменной было ограничено отдельной меткой случая.
"Ошибка перепрыгивания инициализации" ??? Только не с моим GCC. При использовании j под меткой может появиться предупреждение «j может быть использован унифицированный», но ошибки нет. Однако в случае переключения возникает ошибка (серьезная ошибка, а не слабое предупреждение).
@Mecki: Это запрещено в C++. ISO C++ '03 - 6.7 / 3: «... Программа, которая перескакивает из точки, в которой локальная переменная с автоматической продолжительностью хранения не входит в область видимости, в точку, в которой она находится в области видимости, неправильно сформирована, если только переменная не имеет тип POD (3.9) и объявлен без инициализатора (8.5) ».
Да, но это не является незаконным в C (по крайней мере, gcc говорит, что это не так). j будет неинициализированным (иметь какое-то случайное число), но компилятор его компилирует. Однако в случае оператора switch компилятор даже не скомпилирует его, и я не вижу разницы между случаем goto / label и случаем switch.
@Mecki: В общем, поведение одного компилятора не обязательно отражает то, что действительно разрешено языком. Я проверил как C'90, так и C'99, и оба стандарта включают пример с инициализацией перехода в операторе switch.
Большинство ответов до сих пор неверны в одном отношении: вы может объявляете переменные после оператора case, но вы не могу инициализируете их:
case 1:
int x; // Works
int y = 0; // Error, initialization is skipped by case
break;
case 2:
...
Как упоминалось ранее, хороший способ обойти это - использовать фигурные скобки для создания области видимости для вашего случая.
Мистер 32, вы неправильно поняли, в чем заключается ваша ошибка: да, она не будет компилироваться, но не потому, что вы объявляете переменную внутри переключателя. Ошибка возникает из-за того, что вы пытаетесь объявить переменную после оператора, что недопустимо в C.
Теперь дни, когда это разрешено в c90 и более новых версиях c
Мой любимый трюк со злым переключателем - использовать if (0), чтобы пропустить ненужную метку case.
switch(val)
{
case 0:
// Do something
if (0) {
case 1:
// Do something else
}
case 2:
// Do something in all cases
}
Но очень злой.
Очень хорошо. Пример того, почему: case 0 и case 1 могут, например, по-разному инициализировать переменную, которая затем используется в случае 2.
Если вы хотите, чтобы и случай 0, и случай 1 не попали в случай 2 (при этом случай 0 не попал в случай 1). Не знаю, действительно ли это полезно, но точно работает.
Вы можете просто перейти к нужной метке с goto без обфускации кода
Я просто хотел выделить стройныйточка. Конструкция переключателя создает целую первоклассную область видимости. Таким образом, можно объявить (и инициализировать) переменную в операторе switch перед первой меткой case, без - дополнительной парой скобок:
switch (val) {
/* This *will* work, even in C89 */
int newVal = 42;
case VAL:
newVal = 1984;
break;
case ANOTHER_VAL:
newVal = 2001;
break;
}
-1 здесь int newVal = 42; никогда не будет казнен. увидеть это codepad.org/PA1quYX3
объявление int newValбуду должно выполняться, но не назначение = 42.
Пока что ответы касались 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();
}
Второй пример НЕ является действительным C++ (тест с vc2010 и gcc 4.6.1 C++ не позволяет пропустить часть инициализации. Сообщение об ошибке gcc: перекрестная инициализация 'int i'
Интересно, что это нормально:
switch (i)
{
case 0:
int j;
j = 7;
break;
case 1:
break;
}
... но это не так:
switch (i)
{
case 0:
int j = 7;
break;
case 1:
break;
}
Я понимаю, что исправить это достаточно просто, но я пока не понимаю, почему первый пример не беспокоит компилятор. Как упоминалось ранее (2 года назад хе-хе), декларация не является причиной ошибки, даже несмотря на логику. Инициализация - это проблема. Если переменная инициализирована и объявлена в разных строках, она компилируется.
Первый не подходит для gcc 4.2: «ошибка: ожидаемое выражение перед int». Как говорят Питер и Мистер 32, «case 0:; int j; ...» и «case 0:; int j = 7; ...» работают и то, и другое. Проблема в C заключается как раз в том, что «case <label>: декларация» не является допустимым синтаксисом C.
Прочитав все ответы и проведя еще несколько исследований, я понял несколько вещей.
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++, согласно спецификации,
помеченное-объявление разрешено, но помеченное -инициализация не разрешено.
См. 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; // 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.
Вы правильно описали проблему C. Но утверждение, что в C++ помеченная инициализация не разрешена, совершенно не соответствует действительности. Нет ничего плохого в маркированной инициализации в C++. Что C++ не позволяет, так это инициализацию перепрыгивать переменной a в область видимости переменной a. Итак, с точки зрения C, проблема связана с этикеткой case VAL:, и вы правильно ее описали. Но с точки зрения C++ проблема связана с этикеткой case ANOTHER_VAL:.
В C++, в отличие от C, объявления - это подмножество операторов.
Стандарт C++ имеет: Можно передать в блок, но не в обход объявлений с инициализацией. Программа, которая перескакивает из точки, где локальная переменная с автоматической продолжительностью хранения не находится в области видимости, к точке, где она находится в области видимости, является плохо сформированной, если только переменная не имеет тип 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;
}
Похоже, что анонимные объекты может объявляются или создаются в операторе switch case по той причине, что на них нельзя ссылаться и как таковые не могут перейти в следующий case. Рассмотрим этот пример, компилируемый на 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;
}
Если вы собираетесь проголосовать против, объясните, почему. Мне любопытно узнать, почему создание анонимного объекта кажется исключением.
не DV, но: Весь вопрос в объявлении / объеме именованных переменных. Временный («анонимный объект» не является термином) не является именованной переменной, не является объявлением и не является предметом области видимости (если не привязан к ссылке const с собственной областью видимости). Это выражение, которое живет и умирает в своем заявлении (где бы оно ни было). Следовательно, это не имеет никакого значения.
Foo(); не является декларацией; речь идет о декларациях.
Этот вопрос является изначально был помечен как [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.
@AnT: я понимаю, почему тот, который исправляет C++, неприменим для C; однако я не могу понять, как он решает проблему C++ с пропуском инициализации в первую очередь? Разве он не пропустил бы объявление и присвоение newVal при переходе на ANOTHER_VAL?
@ legends2k: Да, все равно пропускает. Однако, когда я говорю «это устраняет проблему», я имею в виду, что это исправляет ошибка компилятора C++. В C++ запрещено пропускать скалярное объявление с инициализатором, но вполне нормально пропускать скалярное объявление без инициализатора. В точке case ANOTHER_VAL: переменная newVal видна, но с неопределенным значением.
Очаровательный. Я нашел этот вопрос после прочтения §A9.3: Compound Statement из K&R C (второе издание). В этой записи упоминается техническое определение составное заявление, которым является {declaration-list[opt] statement-list[opt]}. Сбитый с толку, поскольку я думал, что объявление БЫЛО утверждением, я поискал его и сразу нашел этот вопрос, пример, в котором указанное несоответствие становится очевидным и фактически перерывы - это программа. Я считаю, что другим решением (для C) было бы поместить другой оператор (возможно, пустой оператор?) перед в объявление, чтобы помеченное заявление был удовлетворен.
К сожалению, я только что заметил, что предложенное мной решение с нулевым оператором уже есть в вашем ответе. Тогда не важно.
Лучшее решение, вероятно, - просто объявить переменную вне оператора switch.
Стоит отметить, что исправление добавления пустого оператора работает только для C99 и далее. В C89 переменные должны быть объявлены в начале их включающего блока.
@AnT Похоже, довольно плохое дизайнерское решение для языка, который должен быть в основном обратно совместимым, чтобы допускать int x; x=42; label:;, но выдает серьезную ошибку для int x=42; label1;. Отказ C принять помеченные объявления кажется таким же произвольным ограничением для C в его текущей форме (но по крайней мере C никогда не рекламировал себя как в основном совместимый с C++).
Кстати, перемещение объявления без инициализации до того, как все case-label исправит его на обоих языках.
Блок 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).
Я написал этот ответ изначально для этот вопрос. Однако, когда я закончил, я обнаружил, что ответ был закрыт. Я разместил это здесь, может быть, кому-то, кому нравятся ссылки на стандарты, это пригодится.
Исходный код, о котором идет речь:
int i;
i = 2;
switch(i)
{
case 1:
int k;
break;
case 2:
k = 1;
cout<<k<<endl;
break;
}
На самом деле есть 2 вопроса:
1. Почему я могу объявить переменную после метки case?
Это потому, что в C++ метка должна иметь форму:
N3337 6,1 / 1
labeled-statement:
...
- attribute-specifier-seqopt
caseconstant-expression:statement...
А в C++заявление также рассматривается как утверждение (в отличие от C):
N3337 6/1:
statement:
...
declaration-statement
...
2. Почему я могу перепрыгнуть через объявление переменной, а затем использовать его?
Потому что: N3337 6,7 / 3
It is possible to transfer into a block, but not in a way that bypasses declarations with initialization. A program that jumps (The transfer from the condition of a switch statement to a case label is considered a jump in this respect.)
from a point where a variable with automatic storage duration is not in scope to a point where it is in scope is ill-formedunless the variable has scalar type, class type with a trivial default constructor and a trivial destructor, a cv-qualified version of one of these types, or an array of one of the preceding types and is declared without an initializer (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;
Для объяснения, основанного на грамматике C BNF, см. stackoverflow.com/questions/1180550/weird-switch-error-in-ob j-c /…