it-swarm-id.com

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 mendeklarasikannya 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 'case'

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

874
Rob

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

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

Pertanyaan ini aku s awalnya ditandai sebagai [C] dan [C++] pada saat yang sama. Kode asli memang tidak valid di C dan C++, tetapi untuk alasan yang tidak terkait sama sekali berbeda. Saya percaya detail penting ini terlewatkan (atau dikaburkan) oleh jawaban yang ada.

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

  • Namun, dalam bahasa C melewati inisialisasi variabel bukan kesalahan. Melompat ke ruang lingkup variabel atas inisialisasi adalah legal dalam C. Ini berarti bahwa variabel dibiarkan tidak diinisialisasi. Kode asli tidak dikompilasi dalam C karena alasan yang sama sekali berbeda. Label case VAL: dalam kode asli dilampirkan pada deklarasi variabel newVal. Dalam bahasa C, deklarasi bukan pernyataan. Mereka tidak dapat diberi label. Dan inilah yang menyebabkan kesalahan ketika kode ini diartikan sebagai kode C.

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

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

  • Dalam kasus C masalah dapat dengan mudah diselesaikan tanpa {}. Cukup tambahkan pernyataan kosong setelah label case VAL: dan kode tersebut 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, tetap tidak valid dari sudut pandang C++.

  • Secara simetris, dalam kasus C++ masalahnya dapat dengan mudah diselesaikan tanpa {}. Hapus saja penginisialisasi 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++, tetap tidak valid dari sudut pandang C.

297
AnT

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

Banyak posting di sini telah menyebutkan bahwa melompati deklarasi dapat mengakibatkan variabel "tidak dideklarasikan". 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 kompiler secara implisit menambahkan inisialisasi, 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;
}

Batasan 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 sepele adalah bahwa ini adalah perbedaan antara C++ dan C. Dalam C, itu bukan kesalahan untuk melompati inisialisasi.

Seperti yang telah disebutkan orang lain, solusinya adalah dengan menambahkan blok bersarang sehingga masa pakai variabel terbatas pada label kasus individu.

131
Richard Corden

Seluruh pernyataan sakelar berada dalam cakupan yang sama. Untuk menyiasatinya, lakukan ini:

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

    case ANOTHER_VAL:
      ...
    break;
}

Catatan tanda kurung.

35
Mark Ingram

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

Case statements are only 'labels'

Dalam C, sesuai dengan spesifikasi,

§6.8.1 Pernyataan Berlabel:

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

Di C tidak ada klausa yang memungkinkan untuk "deklarasi 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 juga tidak mengkompilasi , lihat http://codepad.org/BXnRD3b . Di sini saya juga mendapatkan kesalahan yang sama.


Dalam C++, sesuai dengan spesifikasi,

label-deklarasi diizinkan tetapi label-inisialisasi tidak diizinkan.

Lihat http://codepad.org/ZmQ0IyDG .


Solusi untuk kondisi seperti itu adalah dua

  1. Gunakan ruang lingkup 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 kasus 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 di 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/PA1quYX .

28
Jeegar Patel

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

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 label case benar-benar mengabaikan batas blok. Ya, ini jahat. Tapi ini sebabnya contoh kode Anda tidak berfungsi. Melompat ke label case sama dengan menggunakan goto, jadi Anda tidak diizinkan melompati variabel lokal dengan konstruktor.

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

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

Sebagian besar balasan sejauh ini salah dalam satu hal: Anda dapat mendeklarasikan variabel setelah pernyataan kasus, tetapi Anda tidak dapat inisialisasi 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 menggunakan ini adalah dengan menggunakan kawat gigi untuk membuat ruang lingkup untuk kasus Anda.

16
MrZebra

Trik jahat beralih favorit saya adalah menggunakan if (0) untuk melewati label case 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
Jeremy

Coba ini:

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

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

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

Alasannya adalah karena mengalokasikan (dan mengklaim kembali) ruang pada stack untuk penyimpanan variabel lokal.

7
Seb Rose

Mempertimbangkan:

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

Dengan tidak adanya pernyataan break, terkadang newVal dideklarasikan dua kali, dan Anda tidak tahu apakah itu sampai runtime. Dugaan saya adalah bahwa batasannya adalah karena jenis kebingungan ini. Akan seperti apa ruang lingkup newVal? Konvensi akan menentukan bahwa itu akan menjadi seluruh blok sakelar (antara kawat gigi).

Saya bukan programmer C++, tetapi di C:

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

Bekerja dengan baik. Mendeklarasikan variabel di dalam blok switch baik-baik saja. Menyatakan setelah penjaga kasus tidak.

6
slim

Seluruh bagian dari saklar adalah konteks deklarasi tunggal. Anda tidak dapat mendeklarasikan variabel dalam pernyataan kasus seperti itu. Coba ini sebagai gantinya:

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

Jika kode Anda mengatakan "int newVal = 42" maka Anda cukup berharap bahwa newVal tidak pernah diinisialisasi. Tetapi jika Anda kebagian pernyataan ini (yang sedang Anda lakukan) maka itulah yang terjadi - newVal ada di ruang lingkup tetapi belum ditugaskan.

Jika itu yang Anda inginkan terjadi maka bahasa tersebut harus membuatnya eksplisit dengan mengatakan "int newVal; newVal = 42;". Kalau tidak, Anda dapat membatasi ruang lingkup newVal untuk satu kasus, yang lebih mungkin apa yang Anda inginkan.

Ini dapat mengklarifikasi hal-hal jika Anda mempertimbangkan contoh yang sama tetapi dengan "const int newVal = 42;"

3
Mike F

Saya hanya ingin menekankan slim 's point . Konstruk saklar menciptakan ruang lingkup seluruh warga negara kelas satu. Jadi, dimungkinkan untuk mendeklarasikan (dan menginisialisasi) variabel dalam pernyataan sakelar sebelum label case pertama, tanpa pasangan braket tambahan:

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

Menarik bahwa ini baik-baik saja:

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

case 1:  
    break;
}

... tapi ini bukan:

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

case 1:  
    break;
}

Saya mendapatkan bahwa perbaikannya cukup sederhana, tapi saya belum mengerti mengapa contoh pertama tidak mengganggu kompiler. Seperti yang disebutkan sebelumnya (2 tahun sebelumnya hehe), deklarasi bukan apa yang menyebabkan kesalahan, bahkan meskipun logika. Inisialisasi adalah masalahnya. Jika variabel diinisialisasi dan dideklarasikan pada baris yang berbeda, itu dikompilasi.

3
Dan

Saya menulis jawaban ini awalnya untuk pertanyaan ini . Namun ketika saya selesai saya menemukan bahwa jawaban telah ditutup. Jadi saya mempostingnya di sini, mungkin seseorang yang suka referensi ke standar akan merasa terbantu.

Kode Asli yang dimaksud:

int i;
i = 2;
switch(i)
{
    case 1: 
        int k;
        break;
    case 2:
        k = 1;
        cout<<k<<endl;
        break;
}

Sebenarnya ada 2 pertanyaan:

1. Mengapa saya dapat mendeklarasikan variabel setelah label case?

Itu karena dalam label C++ harus dalam bentuk:

N3337 6.1/1

pernyataan berlabel:

...

  • atribut-specifier-seqopt caseconstant-expression: statement

...

Dan dalam C++ pernyataan pernyataan juga dianggap sebagai pernyataan (sebagai lawan dari C):

N3337 6/1:

pernyataan :

...

deklarasi-pernyataan

...

2. Mengapa saya bisa melompati deklarasi variabel dan kemudian menggunakannya?

Karena: N3337 6.7/3

Dimungkinkan untuk mentransfer ke blok, tetapi tidak dengan cara yang melewati deklarasi dengan inisialisasi . Program yang melompati ( transfer dari kondisi pernyataan peralihan ke label kasus dianggap lompatan dalam hal ini.)

dari titik di mana variabel dengan durasi penyimpanan otomatis tidak dalam cakupan ke titik di mana itu dalam ruang lingkup tidak terbentuk kecuali variabel memiliki tipe skalar , tipe kelas dengan konstruktor standar sepele dan destruktor trivial, versi yang memenuhi syarat cv dari salah satu jenis ini, atau larik salah satu dari jenis sebelumnya. ketik dan dideklarasikan tanpa penginisialisasi (8.5).

Karena k adalah dari jenis skalar , dan tidak diinisialisasi pada titik deklarasi melompati deklarasi itu mungkin. Ini setara secara semantik:

goto label;

int x;

label:
cout << x << endl;

Namun itu tidak akan mungkin, jika x diinisialisasi pada saat deklarasi:

 goto label;

    int x = 58; //error, jumping over declaration with initialization

    label:
    cout << x << endl;
3
PcAF

Sejauh ini jawabannya adalah untuk C++.

Untuk C++, Anda tidak dapat melompati inisialisasi. Anda bisa dalam C. Namun, dalam C, deklarasi bukan pernyataan, dan label kasus harus diikuti oleh pernyataan.

Jadi, valid (tapi jelek) C, C++ tidak valid

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

Sebaliknya, dalam C++, deklarasi adalah pernyataan, jadi berikut ini adalah C++ yang valid, C yang tidak valid

switch (something)
{
  case 1:
    do_something();
    break;
  case 2:
    int i = 12;
    do_something_else();
}
3
Peter

Variabel baru dapat dideklarasikan hanya pada lingkup blok. Anda perlu menulis sesuatu seperti ini:

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

Tentu saja, newVal hanya memiliki ruang ...

Ceria, Ralph

1
Ralph Hempel

Blok switchtidak sama dengan suksesi if/else if blok. Saya terkejut tidak ada jawaban lain yang menjelaskannya dengan jelas.

Pertimbangkan pernyataan switch ini:

switch (value) {
    case 1:
        int a = 10;
        break;
    case 2:
        int a = 20;
        break;
}

Mungkin mengejutkan, tetapi kompiler tidak akan melihatnya sebagai if/else if sederhana. Ini akan menghasilkan kode berikut:

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

Pernyataan case dikonversi menjadi label dan kemudian dipanggil dengan goto. Tanda kurung membuat lingkup baru dan mudah untuk melihat sekarang mengapa Anda tidak dapat mendeklarasikan dua variabel dengan nama yang sama dalam blok switch.

Ini mungkin terlihat aneh, tetapi perlu untuk mendukung fallthrough (yaitu, tidak menggunakan break untuk membiarkan eksekusi berlanjut ke case berikutnya).

1
Dalmas

newVal ada di seluruh lingkup switch tetapi hanya diinisialisasi jika ekstremitas VAL dipukul. Jika Anda membuat blok di sekitar kode dalam VAL itu harus OK.

0
marijne

C++ Standard memiliki: Dimungkinkan untuk mentransfer ke dalam blok, tetapi tidak dengan cara yang melewati deklarasi dengan inisialisasi. Sebuah program yang melompat dari titik di mana variabel lokal dengan durasi penyimpanan otomatis tidak dalam ruang lingkup ke titik di mana ruang lingkupnya tidak terbentuk kecuali variabel tersebut memiliki tipe POD (3.9) dan dideklarasikan tanpa penginisialisasi (8.5).

Kode untuk menggambarkan aturan ini:

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

Kode untuk menunjukkan efek penginisialisasi:

#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
Jingguo Yao