Pertanyaan Mengapa variabel tidak dapat dideklarasikan dalam pernyataan switch?


Saya selalu bertanya-tanya ini - mengapa Anda tidak dapat mendeklarasikan variabel setelah label kasus dalam pernyataan switch? Di C + + Anda dapat mendeklarasikan variabel cukup banyak di mana saja (dan menyatakan bahwa mereka mendekati penggunaan pertama jelas merupakan hal yang baik) tetapi yang berikut ini masih tidak berfungsi:

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

Di atas memberi saya kesalahan berikut (MSC):

inisialisasi 'newVal' dilewati oleh label 'kasus'

Ini tampaknya menjadi batasan dalam bahasa lain juga. Mengapa ini menjadi masalah?


788
2017-09-18 13:11


asal


Jawaban:


Case hanya pernyataan label. Ini berarti kompiler akan menafsirkan ini sebagai lompatan langsung ke label. Di C ++, masalah di sini adalah salah satu ruang lingkup. Kurung keriting Anda mendefinisikan ruang lingkup sebagai segala sesuatu di dalam switch pernyataan. Ini berarti Anda memiliki ruang lingkup di mana lompatan akan dilakukan lebih jauh ke kode yang melewati inisialisasi. Cara yang benar untuk menangani ini adalah dengan menentukan ruang lingkup khusus untuk itu case pernyataan dan tentukan variabel Anda di dalamnya.

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

967
2017-09-18 13:17



Pertanyaan ini ditandai sebagai [C] dan [C ++] pada saat yang bersamaan. Kode asli memang tidak valid baik dalam C dan C ++, tetapi untuk alasan yang sama sekali tidak berkaitan. Saya yakin detail penting ini terlewatkan (atau dikaburkan) oleh jawaban yang ada.

  • Dalam C ++, kode ini tidak valid karena case ANOTHER_VAL: label melompat ke dalam lingkup variabel newVal melewati inisialisasi. Lompatan yang memotong inisialisasi objek lokal adalah ilegal di C ++. Sisi masalah ini ditangani dengan benar oleh sebagian besar jawaban.

  • Namun, dalam bahasa C melewati inisialisasi variabel bukanlah kesalahan. Melompat ke dalam lingkup variabel di atas inisialisasi adalah legal dalam C. Ini hanya berarti bahwa variabel tersebut tidak terinisialisasi. Kode asli tidak dikompilasi dalam C untuk alasan yang sama sekali berbeda. Label case VAL: dalam kode asli dilampirkan ke deklarasi variabel newVal. Dalam deklarasi bahasa C bukan pernyataan. Mereka tidak dapat diberi label. Dan inilah yang menyebabkan kesalahan ketika kode ini ditafsirkan sebagai kode C.

    switch (val)  
    {  
    case VAL:             /* <- C error is here */
      int newVal = 42;  
      break;
    case ANOTHER_VAL:     /* <- C++ error is here */
      ...
      break;
    }
    

Menambahkan ekstra {} blok memperbaiki masalah C ++ dan C, meskipun masalah ini terjadi sangat berbeda. Di sisi C ++ itu membatasi ruang lingkup newVal, memastikan itu case ANOTHER_VAL: tidak lagi melompat ke lingkup itu, yang menghilangkan masalah C ++. Di sisi C yang ekstra {} memperkenalkan pernyataan majemuk, sehingga membuat case VAL: label untuk diterapkan ke pernyataan, yang menghilangkan masalah C.

  • Dalam kasus C masalah dapat dengan mudah dipecahkan tanpa {}. Cukup tambahkan pernyataan kosong setelah case VAL: label dan kode akan menjadi valid

    switch (val)  
    {  
    case VAL:;            /* Now it works in C! */
      int newVal = 42;  
      break;
    case ANOTHER_VAL:  
      ...
      break;
    }
    

    Perhatikan bahwa meskipun sekarang valid dari sudut pandang C, itu tetap tidak valid dari sudut pandang C ++.

  • Secara simetris, dalam kasus C ++ masalahnya dapat dengan mudah dipecahkan tanpa {}. Hapus saja initializer dari deklarasi variabel dan kode akan menjadi valid

    switch (val)  
    {  
    case VAL: 
      int newVal;
      newVal = 42;  
      break;
    case ANOTHER_VAL:     /* Now it works in C++! */
      ...
      break;
    }
    

    Perhatikan bahwa meskipun sekarang valid dari sudut pandang C ++, itu tetap tidak valid dari sudut pandang C.


236
2017-11-07 08:12



Baik. Hanya untuk memperjelas ini secara ketat tidak ada hubungannya dengan deklarasi. Ini hanya berhubungan dengan "melompati inisialisasi" (ISO C ++ '03 6.7 / 3)

Banyak tulisan di sini yang menyebutkan bahwa melompati deklarasi dapat mengakibatkan variabel "tidak dinyatakan". Ini tidak benar. Objek POD dapat dideklarasikan tanpa penginisialisasi tetapi akan memiliki nilai tak tentu. Sebagai contoh:

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

Di mana objek adalah non-POD atau agregat kompilator secara implisit menambahkan penginisialisasi, sehingga tidak mungkin untuk melompati deklarasi seperti itu:

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

Keterbatasan ini tidak terbatas pada pernyataan switch. Ini juga merupakan kesalahan untuk menggunakan 'goto' untuk melompati inisialisasi:

goto LABEL;    // Error jumping over initialization
int j = 0; 
LABEL:
  ;

Sedikit hal-hal sepele adalah bahwa ini adalah perbedaan antara C ++ dan C. In C, itu bukan kesalahan untuk melompati inisialisasi.

Seperti yang telah disebutkan orang lain, solusinya adalah dengan menambahkan blok bersarang sehingga umur variabel dibatasi pada label kasus individual.


125
2017-09-18 13:54



Seluruh pernyataan switch berada dalam lingkup yang sama. Untuk mengatasinya, lakukan ini:

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

    case ANOTHER_VAL:
      ...
    break;
}

Catatan kurung.


35
2017-09-18 13:13



Setelah membaca semua jawaban dan beberapa penelitian, saya mendapatkan beberapa hal.

Case statements are only 'labels'

Di C, sesuai spesifikasi,

§6.8.1 Pernyataan Berlabel:

labeled-statement:
    identifier : statement
    case constant-expression : statement
    default : statement

Dalam C tidak ada klausul yang memungkinkan untuk "pernyataan berlabel". Itu bukan bagian dari bahasa.

Begitu

case 1: int x=10;
        printf(" x is %d",x);
break;

Ini tidak akan dikompilasi, Lihat http://codepad.org/YiyLQTYw. GCC memberi kesalahan:

label can only be a part of statement and declaration is not a statement

Bahkan

  case 1: int x;
          x=10;
            printf(" x is %d",x);
    break;

ini adalah juga tidak dikompilasi, Lihat http://codepad.org/BXnRD3bu. Di sini saya juga mendapatkan kesalahan yang sama.


Di C ++, sesuai spesifikasi,

deklarasi berlabel diperbolehkan tetapi diberi label-inisialisasi tidak diizinkan.

Lihat http://codepad.org/ZmQ0IyDG.


Solusi untuk kondisi seperti itu adalah dua

  1. Gunakan cakupan baru menggunakan {}

    case 1:
           {
               int x=10;
               printf(" x is %d", x);
           }
    break;
    
  2. Atau gunakan pernyataan dummy dengan label

    case 1: ;
               int x=10;
               printf(" x is %d",x);
    break;
    
  3. Deklarasikan variabel sebelum beralih () dan inisialisasi dengan nilai yang berbeda dalam pernyataan jika memenuhi kebutuhan Anda

    main()
    {
        int x;   // Declare before
        switch(a)
        {
        case 1: x=10;
            break;
    
        case 2: x=20;
            break;
        }
    }
    

Beberapa hal lagi dengan pernyataan switch

Jangan pernah menulis pernyataan apa pun dalam sakelar yang bukan bagian dari label apa pun, karena tidak akan pernah dieksekusi:

switch(a)
{
    printf("This will never print"); // This will never executed

    case 1:
        printf(" 1");
        break;

    default:
        break;
}

Lihat http://codepad.org/PA1quYX3.


26
2017-12-18 06:33



Anda tidak dapat melakukan ini, karena case label sebenarnya hanya titik masuk ke blok yang mengandung.

Ini paling jelas diilustrasikan oleh Perangkat Duff. Berikut beberapa kode dari Wikipedia:

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

Perhatikan bagaimana case label benar-benar mengabaikan batas blok. Ya, ini jahat. Tapi inilah mengapa contoh kode Anda tidak berfungsi. Melompat ke a case label sama dengan menggunakan goto, jadi Anda tidak diizinkan untuk melompati variabel lokal dengan konstruktor.

Seperti yang telah ditunjukkan oleh beberapa poster lain, Anda harus memasukkan blok Anda sendiri:

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

21
2017-09-18 13:15



Sebagian besar jawaban sejauh ini salah dalam satu hal: Anda bisa mendeklarasikan variabel setelah pernyataan kasus, tetapi Anda tidak bisa menginisialisasi mereka:

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

Seperti yang disebutkan sebelumnya, cara yang bagus untuk menggunakannya adalah dengan menggunakan tanda kurung untuk membuat ruang lingkup untuk kasus Anda.


16
2017-09-18 14:00



Trik sulap favorit saya adalah menggunakan if (0) untuk melompati label kasus yang tidak diinginkan.

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

Tapi sangat jahat.


12
2017-09-18 17:02



Coba ini:

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

10
2017-09-18 13:14



Anda dapat mendeklarasikan variabel dalam pernyataan switch jika Anda memulai blok baru:

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

Alasannya adalah dengan mengalokasikan (dan reklamasi) ruang pada stack untuk penyimpanan variabel lokal (s).


7
2017-09-18 13:15