пример - while switch c++




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

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

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

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

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

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


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


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


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

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

    case ANOTHER_VAL:
      ...
    break;
}

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


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

switch (val)  
{  
case VAL:
{
  // This will work
  int newVal = 42;
  break;
}
case ANOTHER_VAL:  
  ...
  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 аналогичен использованию goto , поэтому вам не разрешается перепрыгивать через локальную переменную с помощью конструктора.

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

switch (...) {
    case FOO: {
        MyObject x(...);
        ...
        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();
}

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

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

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

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<<k<<endl;
        break;
}

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

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 считается прыжком в этом отношении.)

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

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


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

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

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 .


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

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


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

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

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





switch-statement