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

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

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

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

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

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

18.09.2008 13:11:55
Для объяснения на основе грамматики C BNF см stackoverflow.com/questions/1180550/weird-switch-error-in-obj-c/...
johne 12.01.2010 03:30:11
Вот действительно хорошее прочтение о выражениях переключателей и метках (ABC :) в целом.
Etherealone 16.08.2012 22:33:40
Я бы сказал: «Почему переменные не могут быть инициализированы в операторе switch, а не объявлены». Так как только объявление переменной дает мне только предупреждение в MSVC.
ZoomIn 9.12.2013 06:21:26
23 ОТВЕТА
РЕШЕНИЕ

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

Правильный способ справиться с этим - определить область действия, специфичную для этого caseоператора, и определить в нем переменную:

switch (val)
{   
case VAL:  
{
  // This will work
  int newVal = 42;  
  break;
}
case ANOTHER_VAL:  
...
break;
}
1128
19.04.2020 17:02:45
Относительно открытия новой области видимости - способствовать удобочитаемости и последовательности в коде. В старые времена вы могли автоматически получать «лишний» фрейм стека, но теперь этого не должно быть в случае любого приличного оптимизирующего компилятора.
Tall Jeff 18.09.2008 14:37:37
Я согласен с Джеффом - слишком легко «принять» область видимости при чтении оператора switch из-за стиля отступа, который использует большинство людей. Мой собственный стиль - всегда открывать новую область видимости для каждого случая / значения по умолчанию, если он длиннее одной строки.
Bids 31.01.2009 16:43:58
workmad3 - Можете ли вы найти мне какой-нибудь компилятор C ++, который будет генерировать новый кадр стека, если вы не объявите какие-либо новые переменные? Вы меня немного обеспокоили, но ни один из G ++ 3.1, Visual C ++ 7 или Intel C ++ 8 не будет генерировать код для новых областей, где вы не объявляете переменные.
Chris Jefferson 7.02.2009 20:11:13
@ workmad3, введя новый блок фигурных скобок, не вызывает новый кадр стека stackoverflow.com/questions/2759371/…
MTVS 14.01.2013 09:40:01
@TallJef Я не знаю, на какие «старые времена» ты ссылаешься. Я никогда не сталкивался с компилятором, в котором все пространство стека для метода не выделяется при вводе метода в течение 40 лет.
user207421 10.04.2017 08:05:47

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

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

    case ANOTHER_VAL:
      ...
    break;
}

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

38
26.04.2015 10:21:36

Попробуй это:

switch (val)
{
    case VAL:
    {
        int newVal = 42;
    }
    break;
}
10
18.09.2008 13:14:33

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

0
18.09.2008 13:15:16

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

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

Причина в том, чтобы выделить (и восстановить) пространство в стеке для хранения локальной переменной (переменных).

7
18.10.2009 06:12:18
Переменная может быть объявлена, но не может быть инициализирована. Кроме того, я почти уверен, что проблема никак не связана со стеком и локальными переменными.
Richard Corden 18.09.2008 14:12:42

Вы не можете сделать это, потому что 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; 
    }
    ...
 }
20
5.09.2010 20:45:12
В реализации этого устройства Duff есть ошибка, которая делает его чрезвычайно медленным: count имеет тип int, поэтому% должен выполнять реальное деление / деление по модулю. Сделайте счет без знака (или еще лучше, всегда используйте size_t для счетчиков / индексов), и проблема исчезнет.
R.. GitHub STOP HELPING ICE 3.07.2010 20:58:30
@R ..: Что ?! В системе дополнения до двух подпись не влияет на модули степенями 2 (это просто AND на младших битах) и не влияет на деления степенями 2, если ваша архитектура процессора имеет арифметическую операцию сдвига вправо ( SARв x86, по сравнению с SHRкоторым для беззнаковых смен).
Chris Jester-Young 2.09.2010 14:00:41
@Chris: я полагаю, что он имеет в виду, когда компилятор должен учитывать отрицательные значения, где «просто AND на младших битах» не выполняется; например, -1% 8 дает -1 в системе дополнения этих двух, используя g ++ (знак в этом случае - реализация, определенная в 5.6 / 4).
Roger Pate 2.09.2010 15:45:12
@ Крис: Я согласен с вами, что R преувеличивает влияние; Я только видел ваш комментарий и знал, что простого И не достаточно.
Roger Pate 2.09.2010 23:21:12
Также стоит отметить, что оригинальный код Википедии предназначен для отправки данных в отображенный в память вывод, который здесь выглядит странно, потому что он не упоминается, и каждый байт копируется в одно и то же местоположение. Можно обойти это, либо добавив postfix ++ к to, либо упомянув сценарий использования для ввода-вывода с отображением в памяти. Совершенно второстепенный по отношению к первоначальному вопросу :-).
Peter 4.03.2011 23:47:05

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

0
18.09.2008 13:15:25

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

switch (val)  
{  
case VAL:
{
  // This will work
  int newVal = 42;
  break;
}
case ANOTHER_VAL:  
  ...
  break;
}
4
18.09.2008 13:16:24
Переменная может быть объявлена, но не может быть инициализирована.
Richard Corden 18.09.2008 14:11:28
@Richard Corden Я уверен, что инициализация будет работать. Вы все еще утверждаете, что это не может быть инициализировано?
chux - Reinstate Monica 13.09.2013 19:07:19

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

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

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

3
18.09.2008 13:21:36

Рассматривать:

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

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

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

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

Работает отлично. Объявление переменной внутри блока переключателей - это нормально. Декларировать после дела охранника нет.

6
18.09.2008 13:22:09
@ Mr.32: на самом деле ваш пример показывает, что printf не выполняется, но в этом случае int x является не оператором, а объявлением, x объявляется, пространство для него резервируется каждый раз, когда функциональная среда складывается, см .: codepad.org/4E9Zuz1e
Petruza 18.12.2011 07:03:32
Я ожидал найти это при чтении заголовка вопроса, потому что вопрос не в объявлении переменных внутри меток case:, а в выражениях switch. И только вы (и VictorH, подчеркивая ваш ответ) на самом деле говорили о переменных в выражениях switch.
cesss 3.01.2018 22:47:34

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

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

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

Ура, Ральф

1
18.09.2008 13:24:17

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

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

136
18.09.2008 13:54:04
"Ошибка, перепрыгивающая через инициализацию" ??? Не с моим GCC. Может выдавать предупреждение «j может использоваться в унифицированном виде» при использовании j под меткой, но ошибки нет. Однако в случае переключения возникает ошибка (серьезная ошибка, а не слабое предупреждение).
Mecki 20.02.2010 01:20:53
@Mecki: это незаконно в C ++. ISO C ++ '03 - 6.7 / 3: "... Программа, переходящая из точки, в которой локальная переменная с автоматическим хранением находится вне области действия, в точку, в которой она находится в области видимости, если переменная не имеет типа POD (3.9) и объявляется без инициализатора (8.5). "
Richard Corden 22.02.2010 18:21:25
Да, но это не запрещено в C (по крайней мере, gcc говорит, что это не так). j будет неинициализирован (иметь некоторое случайное число), но компилятор его скомпилирует. Однако в случае оператора switch компилятор даже не скомпилирует его, и я не вижу разницы между регистром goto / label и регистром switch.
Mecki 22.02.2010 22:01:05
@Mecki: В общем, поведение отдельного компилятора не обязательно отражает то, что фактически разрешено языком. Я проверил как C'90, так и C'99, и оба стандарта включают пример с перепрыгивающей инициализацией в операторе switch.
Richard Corden 28.02.2010 22:23:36

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

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

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

16
18.09.2008 14:00:31
Мистер 32, вы неправильно поняли, в чем ваша ошибка: да, это не скомпилируется, но не потому, что вы объявляете переменную внутри переключателя. Ошибка в том, что вы пытаетесь объявить переменную после оператора, что недопустимо в C.
MrZebra 18.12.2011 15:26:12
Теперь дни, которые законны в c90 и более новая версия c
Jeegar Patel 19.12.2011 04:39:15

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

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

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

12
18.09.2008 17:02:47
Очень хорошо. Пример того, почему: case 0 и case 1 могут, например, инициализировать переменную по-другому, что затем используется в случае 2.
hlovdal 19.03.2009 10:02:32
Если вы хотите, чтобы случай 0 и случай 1 проходили через случай 2. (без случая 0 - через случай 1). Не знаю, действительно ли это полезно, но точно работает.
Petruza 18.12.2011 06:55:35
Вы можете просто перейти к нужной метке gotoбез обфускации кода
SomeWittyUsername 24.09.2016 19:12:31

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

switch (val) {  
  /* This *will* work, even in C89 */
  int newVal = 42;  
case VAL:
  newVal = 1984; 
  break;
case ANOTHER_VAL:  
  newVal = 2001;
  break;
}
3
23.05.2017 11:55:02
-1 здесь int newVal = 42; никогда не будет казнен. см. этот codepad.org/PA1quYX3
Jeegar Patel 18.12.2011 05:55:52
декларация int newVal будет выполнена, но не = 42присвоение.
Petruza 18.12.2011 07:10:27

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

Для C ++ вы не можете перепрыгнуть через инициализацию. Вы можете сделать это в C. Однако в C декларация не является оператором, и после меток должны следовать операторы.

Итак, допустимый (но некрасивый) 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();
}
3
2.09.2009 15:08:23
Второй пример НЕ подходит для C ++ (тест с vc2010 и gcc 4.6.1. C ++ не позволяет пропустить часть инициализации. Сообщение об ошибке gcc: перекрестная инициализация 'int i'
zhaorufei 26.12.2011 09:50:09

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

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

case 1:  
    break;
}

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

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

case 1:  
    break;
}

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

3
23.03.2011 02:20:02
Первый не подходит для gcc 4.2: «ошибка: ожидаемое выражение перед« int »». Как сказал Питер и г-н 32, «case 0:; int j; ...» и «case 0:; int j = 7; ...» работают оба. Проблема в C только в том, что case <label>: объявление не является допустимым синтаксисом языка C.
dubiousjim 30.05.2012 21:21:56

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

Case statements are only 'labels'

В С, согласно спецификации,

§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 () и инициализируйте ее различными значениями в выражении 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 .

30
12.07.2016 12:51:39
Вы правильно описали проблему C. Но утверждение о том, что в C ++ помеченная инициализация не допускается, совершенно не соответствует действительности. Нет ничего плохого в маркированной инициализации в C ++. Что не позволяет C ++, так это перепрыгивать через инициализацию переменной aв область видимости переменной a. Итак, с точки зрения C, проблема с case VAL:меткой, и вы правильно ее описали. Но с точки зрения C ++ проблема заключается в case ANOTHER_VAL:метке.
AnT 7.11.2013 08:14:42
В C ++, в отличие от C, объявления являются подмножеством операторов.
Keith Thompson 7.08.2016 00:01:53

Стандарт 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;
}
0
10.07.2012 06:48:19

Похоже, что анонимные объекты могут быть объявлены или созданы в операторе switch 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;
}
0
8.02.2013 10:55:29
Если вы собираетесь проголосовать, объясните почему. Мне любопытно узнать, почему создание анонимного объекта является исключением.
Olumide 31.05.2016 14:03:37
не DV, но: весь вопрос о декларировании / области именованных переменных. Временный («анонимный объект» не является термином) не является именованной переменной, а также не является декларацией и не является предметом области действия (если он не связан со constссылкой с собственной областью действия). Это выражение, которое живет и умирает в своем утверждении (где бы оно ни было). Поэтому это совершенно не имеет значения.
underscore_d 17.07.2016 17:55:44
Foo();не является декларацией; вопрос о декларациях.
M.M 6.07.2017 04:02:47

Этот вопрос будет первоначально был помечен как [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.

325
2.07.2019 02:41:04
@AnT: я понимаю, почему тот, который исправляет C ++, не применим для C; Тем не менее, я не могу понять, как это решает проблему C ++ пропуска инициализации в первую очередь? Не будет ли он по-прежнему пропускать объявление и присваивание, newValкогда он переходит к ANOTHER_VAL?
legends2k 19.06.2015 09:08:31
@ legends2k: Да, это все еще пропускает. Однако когда я говорю «это решает проблему», я имею в виду, что это исправляет ошибку компилятора C ++ . В C ++ недопустимо пропускать скалярное объявление с помощью инициализатора , но совершенно нормально пропускать скалярное объявление без инициализатора . В case ANOTHER_VAL:точке переменная newValвидна, но с неопределенным значением.
AnT 19.06.2015 14:45:46
Захватывающий. Я нашел этот вопрос после прочтения §A9.3: Compound Statementиз K & R C (второе издание). В записи упоминается техническое определение составного заявления, которое есть {declaration-list[opt] statement-list[opt]}. Смущенный, потому что я думал, что заявление БЫЛО заявлением, я посмотрел его и сразу нашел этот вопрос, пример, где указанное несоответствие становится очевидным и фактически нарушает программу. Я полагаю, что другим решением (для C) было бы поместить другое заявление (возможно, нулевое утверждение?) Перед объявлением, чтобы маркированное утверждение было выполнено.
Braden Best 4.02.2017 10:04:02
К сожалению, я только что заметил, что предложенное мной решение с нулевым оператором уже есть в вашем ответе. Тогда не беспокойся.
Braden Best 4.02.2017 10:19:59
Стоит отметить, что исправление добавления пустого оператора работает только для C99 и выше. В C89 переменные должны быть объявлены в начале их включающего блока.
Arthur Tacca 12.12.2019 16:40:58

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

1
1.01.2015 18:17:08

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

Оригинальный код в вопросе:

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

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