Mengapa variabel tidak dapat dideklarasikan dalam pernyataan switch?


945

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 hal berikut 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 tampaknya juga menjadi batasan dalam bahasa lain. Mengapa ini menjadi masalah?


10
Untuk penjelasan berdasarkan tata bahasa C BNF, lihat stackoverflow.com/questions/1180550/weird-switch-error-in-obj-c/…
johne

Berikut ini adalah bacaan yang sangat bagus tentang pernyataan pergantian dan label (ABC :) secara umum.
Etherealone

4
Saya akan mengatakan 'Mengapa variabel tidak dapat diinisialisasi dalam pernyataan beralih daripada dideklarasikan'. Karena hanya mendeklarasikan variabel hanya memberi saya peringatan di MSVC.
ZoomIn

Jawaban:


1144

Case hanya pernyataan 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 switchpernyataan. Ini berarti bahwa Anda memiliki ruang lingkup di mana lompatan akan dilakukan lebih lanjut ke dalam kode yang melewatkan inisialisasi.

Cara yang benar untuk menangani ini adalah dengan menentukan ruang lingkup khusus untuk casepernyataan itu dan mendefinisikan variabel Anda di dalamnya:

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

94
Relatif untuk membuka ruang lingkup baru - mendukung keterbacaan dan konsistensi dalam kode. Di masa lalu, Anda mungkin secara otomatis mendapatkan bingkai tumpukan "ekstra", tetapi sekarang seharusnya tidak demikian halnya dengan kompiler pengoptimalan yang layak.
Tall Jeff

10
Saya setuju dengan Jeff - terlalu mudah untuk "menganggap" lingkup ketika membaca pernyataan beralih karena gaya indentasi yang digunakan kebanyakan orang. Gaya saya sendiri adalah untuk selalu membuka ruang lingkup baru untuk setiap case / default jika panjangnya lebih dari satu baris.
Tawaran

39
workmad3 - Dapatkah Anda menemukan saya kompiler C ++ sama sekali yang akan menghasilkan bingkai tumpukan baru jika Anda tidak mendeklarasikan variabel baru? Anda membuat saya khawatir secara singkat, tetapi tidak ada G ++ 3.1, Visual C ++ 7 atau Intel C ++ 8 yang akan menghasilkan kode untuk lingkup baru di mana Anda tidak mendeklarasikan variabel apa pun.
Chris Jefferson

10
@ workmad3 dengan memasukkan blok kurung kurawal baru tidak menyebabkan bingkai stack baru stackoverflow.com/questions/2759371/...
MTVS

3
@AllJef Saya tidak tahu 'hari tua' apa yang Anda maksud. Saya belum pernah menemukan kompiler di mana semua ruang stack untuk suatu metode tidak dialokasikan ketika metode dimasukkan, dalam 40 tahun.
Marquis of Lorne

333

Pertanyaan ini awalnya ditandai sebagai [C] dan [C ++] secara bersamaan. Kode asli memang tidak valid di C dan C ++, tetapi untuk alasan yang tidak terkait sama sekali berbeda.

  • Dalam C ++ kode ini tidak valid karena case ANOTHER_VAL:label melompat ke dalam lingkup variabel newValmelewati inisialisasi. Melompati pemintas inisialisasi objek otomatis 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 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 {}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 ruang lingkup itu, yang menghilangkan masalah C ++. Di sisi C yang ekstra {}memperkenalkan pernyataan majemuk, sehingga membuat case VAL:label untuk diterapkan pada pernyataan, yang menghilangkan masalah C.

  • Dalam kasus C masalah dapat dengan mudah diselesaikan 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, tetap tidak valid dari sudut pandang C ++.

  • Secara simetris, dalam kasus C ++ masalah 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.


4
@ ANT: Saya mengerti mengapa yang memperbaiki C ++ tidak berlaku untuk C; Namun, saya tidak dapat memahami bagaimana cara memperbaiki masalah C ++ dari melewatkan inisialisasi di tempat pertama? Bukankah itu masih melewatkan deklarasi dan penugasan newValketika melompat ke ANOTHER_VAL?
legends2k

13
@ legends2k: Ya, masih dilewati. Namun, ketika saya mengatakan "itu memperbaiki masalah", maksud saya bahwa itu memperbaiki kesalahan kompilasi C ++ . Dalam C ++ adalah ilegal untuk melewati deklarasi skalar dengan penginisialisasi , tetapi sangat baik untuk melewatkan deklarasi skalar tanpa penginisialisasi . Pada case ANOTHER_VAL:titik variabel newValterlihat, tetapi dengan nilai tak tentu.
AnT

3
Menarik. Saya menemukan pertanyaan ini setelah membaca §A9.3: Compound Statementdari K&R C (edisi kedua). Entri menyebutkan definisi teknis dari pernyataan majemuk yang {declaration-list[opt] statement-list[opt]}. Bingung, karena saya pikir deklarasi adalah pernyataan, saya mencarinya dan segera menemukan pertanyaan ini, contoh di mana perbedaan itu menjadi jelas dan benar-benar merusak sebuah program. Saya percaya solusi lain (untuk C) akan menempatkan pernyataan lain (mungkin pernyataan nol?) Sebelum deklarasi sehingga pernyataan berlabel puas.
Braden Best

Ups, saya baru memperhatikan bahwa solusi pernyataan nol yang saya sarankan sudah ada dalam jawaban Anda. Lupakan saja.
Braden Best

3
Perlu dicatat bahwa perbaikan menambahkan pernyataan kosong hanya berfungsi untuk C99 dan seterusnya. Dalam C89, variabel harus dideklarasikan pada awal blok melampirkan mereka.
Arthur Tacca

136

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 penginisialisasi, dan 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-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.


2
"Kesalahan melompati inisialisasi" ??? Tidak dengan GCC saya. Ini dapat memberikan peringatan "j dapat digunakan unitialized" saat menggunakan j di bawah label, tetapi tidak ada kesalahan. Namun, dalam hal sakelar, ada kesalahan (kesalahan keras, bukan peringatan lemah).
Mecki

9
@Mecki: Ini ilegal di C ++. ISO C ++ '03 - 6.7 / 3: "... 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 dinyatakan tanpa penginisialisasi (8.5). "
Richard Corden

1
Ya, tapi itu tidak ilegal di C (setidaknya gcc mengatakan itu bukan). j akan diinisialisasi (memiliki beberapa nomor acak), tetapi kompiler mengkompilasinya. Namun, dalam kasus pernyataan switch, kompiler bahkan tidak akan mengkompilasinya dan saya gagal melihat perbedaan antara kasus goto / label dan kasus switch.
Mecki

8
@Mecki: Secara umum perilaku compiler tunggal tidak selalu mencerminkan apa yang sebenarnya diizinkan oleh bahasa. Saya telah memeriksa C'90 dan C'99 dan kedua standar menyertakan contoh dengan lompatan inisialisasi dalam pernyataan switch.
Richard Corden

38

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

Perhatikan tanda kurung.


30

Setelah membaca semua jawaban dan beberapa 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 bukan kompilasi , lihat http://codepad.org/BXnRD3bu . 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 merupakan 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 .


2
Anda dengan benar menggambarkan masalah C. Tetapi pernyataan bahwa inisialisasi C ++ berlabel tidak diperbolehkan sama sekali tidak benar. Tidak ada yang salah dengan inisialisasi berlabel di C ++. Apa yang tidak diizinkan oleh C ++ adalah melompati inisialisasi variabel ake dalam lingkup variabel a. Jadi, dari sudut pandang C, masalahnya ada pada case VAL:label dan Anda menggambarkannya dengan benar. Tapi dari sudut pandang C ++, masalahnya ada pada case ANOTHER_VAL:label.
AnT

Dalam C ++, tidak seperti dalam C, deklarasi adalah bagian dari pernyataan.
Keith Thompson

20

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

Ini paling jelas digambarkan 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 caselabel benar-benar mengabaikan batas blok. Ya, ini jahat. Tapi ini sebabnya contoh kode Anda tidak berfungsi. Melompat ke caselabel 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; 
    }
    ...
 }

1
Implementasi perangkat Duff ini memiliki bug yang membuatnya sangat lambat: jumlah adalah ketik int sehingga% harus melakukan operasi pembagian / modulo nyata. Buat hitungan tidak ditandatangani (atau lebih baik lagi, selalu gunakan size_t untuk jumlah / indeks) dan masalahnya hilang.
R .. GitHub BERHENTI MEMBANTU ICE

1
@R ..: Apa ?! Dalam sistem komplemen dua, signness tidak memengaruhi modulos dengan kekuatan 2 (itu hanya DAN pada bit bawah), dan tidak memengaruhi divisi dengan kekuatan 2 selama arsitektur prosesor Anda memiliki operasi pergeseran kanan aritmatika ( SARdalam x86, dibandingkan SHRyang untuk shift yang tidak ditandatangani).
Chris Jester-Young

@ Chris: Saya percaya maksudnya ketika kompiler harus mengizinkan nilai negatif di mana "hanya DAN pada bit bawah" tidak berlaku; misalnya, -1% 8 memberikan -1 pada sistem komplemen dua ini menggunakan g ++ (tanda dalam hal ini adalah implementasi yang didefinisikan per 5.6 / 4).

3
@ Chris: Saya setuju dengan Anda bahwa R melebih-lebihkan dampaknya; Saya hanya melihat komentar Anda dan tahu DAN sederhana tidak cukup.

1
Juga perlu dicatat adalah kode Wikipedia asli untuk mengirim data ke output yang dipetakan memori, yang terlihat aneh di sini karena tidak disebutkan dan setiap byte disalin ke lokasi "ke" yang sama. Bisa menyiasatinya dengan menambahkan postfix ++ ke to, atau menyebutkan use case adalah untuk memori yang dipetakan IO. Benar-benar perangkat untuk pertanyaan asli :-).
Peter

16

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


1
Tn. 32 Anda salah paham tentang kesalahan Anda: ya itu tidak akan dikompilasi tetapi bukan karena Anda mendeklarasikan variabel di dalam sakelar. Kesalahannya adalah karena Anda mencoba mendeklarasikan variabel setelah pernyataan, yang ilegal di C.
MrZebra

1
Sekarang hari yang sah di c90 dan versi yang lebih baru dari c
Jeegar Patel

12

Trik beralih kejahatan 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.


Sangat bagus. Contoh mengapa: kasus 0 dan kasus 1 kekuatan misalnya menginisialisasi variabel berbeda yang kemudian digunakan dalam kasus 2.
hlovdal

1
Jika Anda ingin kasing 0 dan kasing 1 turun melalui kasing 2. (tanpa kasing 0 kasing kasing kasing 1). Tidak tahu apakah itu benar-benar bermanfaat, tetapi tentu saja berhasil.
Petruza

1
Anda hanya dapat melompat ke label yang diperlukan gototanpa kebingungan kode
SomeWittyUsername

10

Coba ini:

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

7

Anda bisa 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.


1
Variabel dapat dideklarasikan, tetapi tidak dapat diinisialisasi. Juga, saya cukup yakin bahwa masalah ini tidak berhubungan dengan variabel lokal dan stack.
Richard Corden

6

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 saklar (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 tidak masalah. Mendeklarasikan setelah penjaga kasus tidak.


3
@ Mr.32: sebenarnya contoh Anda menunjukkan bahwa printf tidak dieksekusi, tetapi dalam kasus ini, int x bukan pernyataan tetapi deklarasi, x dideklarasikan, ruang untuk dicadangkan setiap kali lingkungan fungsi ditumpuk, lihat: codepad.org/4E9Zuz1e
Petruza

Saya mengharapkan untuk menemukan ini ketika membaca judul pertanyaan, karena pertanyaannya bukan tentang mendeklarasikan variabel dalam label "case:", tetapi dalam pernyataan pergantian. Dan hanya Anda (dan VictorH, yang menekankan jawaban Anda) yang benar-benar berbicara tentang variabel dalam pernyataan pergantian.
cesss

4

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

Variabel dapat dideklarasikan, tetapi tidak dapat diinisialisasi.
Richard Corden

@ Richard Corden Saya yakin inisialisasi akan berhasil. Apakah Anda masih menegaskan itu tidak dapat diinisialisasi?
chux - Reinstate Monica

3

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 berada dalam 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

Saya hanya ingin menekankan ramping 's titik . Konstruk saklar menciptakan ruang lingkup seluruh warga negara kelas satu. Jadi, dimungkinkan untuk mendeklarasikan (dan menginisialisasi) variabel dalam pernyataan switch sebelum label kasus 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;
}

-1 sini int newVal = 42; tidak akan dieksekusi. lihat codepad.org/PA1quYX3
Jeegar Patel

4
deklarasi int newVal akan dieksekusi, tetapi bukan = 42penugasan.
Petruza

3

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

1
Contoh kedua adalah TIDAK valid C ++ (uji dengan vc2010 dan gcc 4.6.1 C ++ tidak memungkinkan lewati bagian inisialisasi. Pesan kesalahan gcc adalah: inisialisasi silang 'int i'
zhaorufei

3

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 bukanlah yang menyebabkan kesalahan, meskipun logika. Inisialisasi adalah masalahnya. Jika variabel diinisialisasi dan dideklarasikan pada baris yang berbeda, itu dikompilasi.


1
Pertama tidak baik pada gcc 4.2: "error: ekspresi yang diharapkan sebelum 'int'". Seperti yang dikatakan Peter dan Mr.32, "case 0:; int j; ..." dan "case 0:; int j = 7; ..." keduanya berfungsi. Masalah dalam C hanyalah "case <label>: declaration" bukan sintaks C yang valid.
dubiousjim

3

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 caselabel?

Itu karena dalam label C ++ harus dalam bentuk:

N3337 6.1 / 1

pernyataan berlabel:

...

  • atribut-specifier-seqopt case constant-expression :statement

...

Dan dalam C++ pernyataan pernyataan juga dianggap sebagai pernyataan (berlawanan dengan 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 dalam blok, tetapi tidak dengan cara yang melewati deklarasi dengan inisialisasi . Program yang melompat ( Transfer dari kondisi pernyataan switch ke label kasus dianggap lompatan dalam hal ini.)

dari titik di mana variabel dengan durasi penyimpanan otomatis tidak dalam ruang lingkup ke titik di mana ia berada dalam ruang lingkup tidak terbentuk kecuali variabel memiliki tipe skalar , tipe kelas dengan konstruktor default sepele dan destruktor trivial, versi berkualifikasi cv dari salah satu jenis ini, atau larik salah satu dari jenis sebelumnya dan dinyatakan tanpa inisialisasi (8.5).

Sejak kadalah tipe skalar , dan tidak dimulai pada titik deklarasi melompat lebih deklarasi itu adalah mungkin. Ini setara secara semantik:

goto label;

int x;

label:
cout << x << endl;

Namun itu tidak akan mungkin, jika xdiinisialisasi pada titik pernyataan:

 goto label;

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

    label:
    cout << x << endl;

1

Variabel-variabel baru dapat didekati 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

Sebuah switchblok tidak sama sebagai suksesi if/else ifblok. Saya terkejut tidak ada jawaban lain yang menjelaskannya dengan jelas.

Pertimbangkan switchpernyataan ini :

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

Mungkin mengejutkan, tetapi kompiler tidak akan melihatnya sebagai sederhana if/else if. 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

The casepernyataan diubah menjadi label dan kemudian disebut dengan goto. Tanda kurung membuat lingkup baru dan mudah untuk melihat sekarang mengapa Anda tidak dapat mendeklarasikan dua variabel dengan nama yang sama di dalam aswitch blok.

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


0

Saya percaya masalahnya adalah bahwa pernyataan itu dilewati, dan Anda mencoba menggunakan var di tempat lain, itu tidak akan dinyatakan.


0

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


0

C ++ Standard memiliki: Dimungkinkan untuk mentransfer ke dalam blok, tetapi tidak dengan cara yang melewati deklarasi dengan inisialisasi. Suatu 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

Tampaknya objek anonim dapat dideklarasikan atau dibuat dalam pernyataan kasus sakelar untuk alasan bahwa mereka tidak dapat dirujuk dan karenanya tidak dapat diteruskan ke kasing kasus berikutnya. Pertimbangkan contoh ini kompilasi pada GCC 4.5.3 dan Visual Studio 2008 (mungkin merupakan masalah kepatuhan sehingga para ahli mempertimbangkan)

#include <cstdlib>

struct Foo{};

int main()
{
    int i = 42;

    switch( i )
    {
    case 42:
        Foo();  // Apparently valid
        break;

    default:
        break;
    }
    return EXIT_SUCCESS;
}

Jika Anda akan memilihnya silakan jelaskan alasannya. Saya ingin tahu mengapa membuat objek anonim tampaknya menjadi pengecualian.
Olumide

1
bukan DV, tapi: Seluruh pertanyaannya adalah tentang mendeklarasikan / lingkup variabel bernama. Sementara ("objek anonim" bukan istilah) bukan variabel bernama, juga bukan deklarasi, juga tidak tunduk pada cakupan (kecuali terikat pada constreferensi dengan cakupannya sendiri). Ini adalah ekspresi yang hidup dan mati dalam pernyataannya (di mana pun itu berada). Karena itu, sama sekali tidak relevan.
underscore_d

Foo();bukan deklarasi; pertanyaannya adalah tentang deklarasi.
MM
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.