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

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

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

Вышеуказанная ситуация дает мне следующую ошибку (MSC):

инициализация "newVal" пропускается меткой "case"

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

Ответы

Ответ 1

Операторы

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

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

Ответ 2

Этот вопрос был первоначально помечен как [C] и [C++] в то же время. Исходный код действительно недействителен как в C, так и в C++, но по совершенно другим не связанным причинам.

  • В C++ этот код недействителен, потому что case ANOTHER_VAL: label попадает в область действия переменной 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 extra {} вводит составной оператор, тем самым делая case VAL: label для применения к оператору, что устраняет проблему 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.

Ответ 3

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.

Ответ 4

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

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

    case ANOTHER_VAL:
      ...
    break;
}

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

Ответ 5

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

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;
}

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

Ответ 6

Вы не можете этого сделать, потому что метки 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; 
    }
    ...
 }

Ответ 7

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

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

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

Ответ 8

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

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

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

Ответ 9

Попробуйте следующее:

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

Ответ 10

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

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

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

Ответ 11

Рассмотрим:

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

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

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

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

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

Ответ 12

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

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

Ответ 13

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

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

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

Ответ 14

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

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

Ответ 15

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

Для С++ вы не можете перепрыгнуть через инициализацию. Вы можете в 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();
}

Ответ 16

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

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

case 1:  
    break;
}

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

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

case 1:  
    break;
}

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

Ответ 17

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

Оригинальный код:

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;

Ответ 18

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

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

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

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

Ответ 19

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).

Ответ 20

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

Ответ 21

Стандарт С++ имеет: Можно передать в блок, но не таким образом, чтобы обходить объявления с инициализацией. Программа, которая перескакивает с точки, где локальная переменная с продолжительностью автоматического хранения не находится в области до точки, где она находится в области видимости, плохо сформирована, если переменная не имеет тип 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;
}

Ответ 22

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

Ответ 23

Похоже, анонимные объекты могут быть объявлены или созданы в операторе 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;
}