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

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

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

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

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

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

23 ответов


Case операторы только метки. Это означает, что компилятор интерпретирует это как переход непосредственно к метке. В 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: метка и код станут действительными

    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.


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

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


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

с меткой-объявление разрешено, но с меткой-инициализация не разрешена.

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

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

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

большинство ответов до сих пор не правы в одном отношении: вы can объявлять переменные после оператора 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;
}

работает нормально. Объявление переменной внутри коммутатора блок в порядке. Объявление после дела охранником не является.


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

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


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

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

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


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

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

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

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

...

  • атрибут-спецификатор-seqopt case constant-expression : statement

...

и C++ декларация считается сообщении (вместо C):

N3337 6/1:

сообщении:

...

декларация-заявление

...

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

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

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

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


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 блок.

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


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


стандарт 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;
}

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


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