Menggunakan {} dalam pernyataan kasus. Mengapa?

101

Apa gunanya menggunakan {dan }dalam sebuah casepernyataan? Biasanya, tidak peduli berapa banyak baris yang ada dalam sebuah casepernyataan, semua baris akan dieksekusi. Apakah ini hanya aturan mengenai kompiler lama / baru atau ada sesuatu di balik itu?

int a = 0;
switch (a) {
  case 0:{
    std::cout << "line1\n";
    std::cout << "line2\n";
    break;
  }
}

dan

int a = 0;
switch (a) {
  case 0:
    std::cout << "line1\n";
    std::cout << "line2\n";
    break;
}
mahmood
sumber
57
Satu kegunaan dapat untuk membatasi ruang lingkup variabel yang dideklarasikan dalam pernyataan kasus.
Abhishek Bansal
1
Terlalu banyak lekukan juga. Kasusnya hanyalah label di dalam blok pernyataan switch: mereka tidak memperkenalkan penumpukan tambahan, jadi harus sejajar dengan switchkata kunci, dan dalam contoh kedua, pernyataan terlampir hanya menjorok sekali. Perhatikan bagaimana Anda memiliki empat spasi de-indent setelah break;.
Kaz
Perhatikan, jawaban yang diterima hanya sebagian benar seperti yang ditunjukkan oleh komentar Jack dan melewatkan beberapa seluk-beluk, yang saya bahas dalam jawaban saya.
Shafik Yaghmour
Sama seperti FYI: dalam C (bahkan C11) daripada C ++, Anda tidak dapat memberi label deklarasi; mereka tidak termasuk dalam kategori sintaksis statement. Di C ++, Anda bisa (salah satu komponen dari kategori sintaksis statementadalah declaration statement).
Jonathan Leffler

Jawaban:

195

The {}menunjukkan blok baru dari lingkup .

Pertimbangkan contoh yang sangat dibuat-buat berikut:

switch (a)
{
    case 42:
        int x = GetSomeValue();
        return a * x;
    case 1337:
        int x = GetSomeOtherValue(); //ERROR
        return a * x;
}

Anda akan mendapatkan kesalahan kompilator karena xsudah ditentukan dalam ruang lingkup.

Memisahkan ini ke sub-cakupannya sendiri akan menghilangkan kebutuhan untuk mendeklarasikan di xluar pernyataan switch.

switch (a)
{
    case 42: {
        int x = GetSomeValue();
        return a * x; 
    }
    case 1337: {
        int x = GetSomeOtherValue(); //OK
        return a * x; 
    }
}
Rotem
sumber
11
Sebenarnya IMO Anda akan mendapatkan kesalahan compiler bahkan jika Anda melewatkan deklarasi kedua variabel x.
Abhishek Bansal
1
Meskipun menggunakan gaya ini secara berlebihan dan meletakkan blok besar di dalam pernyataan sakelar akan membuatnya tidak dapat dibaca untuk mengikuti kasing. Saya lebih suka menyimpan pernyataan kecil.
masoud
2
@Bayu_joo Saya tahu pasti bahwa MS Visual Studio 2010 akan memiliki perilaku yang ditunjukkan Abhishek: itu tidak akan mengkompilasi deklarasi variabel apa pun di dalam kasus (kecuali Anda menggunakan tanda kurung keriting untuk menunjukkan ruang lingkup baru dalam kasus itu). Apakah itu sesuai dengan standar, saya tidak tahu.
KRyan
1
@KRyan: tidak, tapi ini adalah alternatif yang jauh lebih aman sehingga saya hampir tidak bisa menyalahkan mereka karena memaksakan ini.
Matthieu M.
6
Bagian 6.7 (3) dari standar (penomoran untuk draf 2005) menetapkan bahwa Anda tidak dapat melompati inisialisasi, jadi Anda tidak dapat melakukan inisialisasi dalam blok kasus.
Jack Aidley
23

TL; DR

Satu-satunya cara Anda dapat mendeklarasikan variabel dengan intializer atau objek non-sepele di dalam kasus adalah dengan memperkenalkan cakupan blok menggunakan {}atau struktur kontrol lain yang memiliki ruang lingkup sendiri seperti loop atau pernyataan if .

Detail berdarah

Kita dapat melihat bahwa kasus hanyalah pernyataan berlabel seperti label yang digunakan dengan pernyataan goto ( ini tercakup dalam standar draf C ++ bagian 6.1 Pernyataan berlabel ) dan kita dapat melihat dari bagian 6.7paragraf 3 bahwa melompati deklarasi tidak diperbolehkan dalam banyak kasus , termasuk yang memiliki inisialisasi:

Dimungkinkan untuk mentransfer ke dalam blok, tetapi tidak dengan cara yang mengabaikan deklarasi dengan inisialisasi. Sebuah program yang melompat 87 dari titik di mana sebuah variabel dengan durasi penyimpanan otomatis tidak dalam ruang lingkup ke titik di mana ia berada dalam ruang lingkup tidak terbentuk kecuali variabel tersebut memiliki tipe skalar, tipe kelas dengan konstruktor standar sepele dan destruktor sepele, versi yang memenuhi syarat cv dari salah satu jenis ini, atau larik dari salah satu jenis sebelumnya dan dideklarasikan tanpa penginisialisasi (8.5).

dan memberikan contoh ini:

void f() {
 // ...
 goto lx; // ill-formed: jump into scope of a

 ly:
  X a = 1;
 // ...
 lx:
  goto ly; // OK, jump implies destructor
          // call for a followed by construction
          // again immediately following label ly
}

Perhatikan, ada beberapa kehalusan di sini, Anda diizinkan untuk melewati deklarasi skalar yang tidak memiliki inisialisasi, misalnya:

switch( n ) 
{
    int x ;
    //int x  = 10 ; 
    case 0:
      x = 0 ;
      break;
    case 1:
      x = 1 ;
      break;
    default:
      x = 100 ;
      break ;
}

benar-benar valid ( contoh langsung ). Tentu saja jika Anda ingin mendeklarasikan variabel yang sama dalam setiap kasus maka mereka masing-masing akan membutuhkan ruang lingkupnya sendiri tetapi bekerja dengan cara yang sama di luar pernyataan switch juga, jadi itu seharusnya tidak menjadi kejutan besar.

Adapun alasan untuk tidak mengizinkan inisialisasi lompatan melewati, laporan kerusakan 467 meskipun mencakup masalah yang sedikit berbeda memberikan kasus yang wajar untuk variabel otomatis :

[...] variabel otomatis, jika tidak diinisialisasi secara eksplisit, dapat memiliki nilai tak tentu ("sampah"), termasuk representasi perangkap, [...]

Mungkin lebih menarik untuk melihat kasus di mana Anda memperluas cakupan dalam sebuah sakelar ke beberapa kasus. Contoh paling terkenal dari ini mungkin adalah perangkat Duff yang akan terlihat seperti ini:

void send( int *to, const int *from, int  count)
{
        int n = (count + 7) / 8;
        switch(count % 8) 
        {
            case 0: do {    *to = *from++;   // <- Scope start
            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);    // <- Scope end
        }
}
Shafik Yaghmour
sumber
6

Ini adalah kebiasaan yang memungkinkan Anda memasukkan deklarasi variabel dengan destruktor yang dihasilkan (atau konflik cakupan) ke dalam caseklausa. Cara lain untuk melihatnya adalah mereka menulis untuk bahasa yang mereka inginkan, di mana semua kontrol aliran terdiri dari blok dan bukan urutan pernyataan.

Yakk - Adam Nevraumont
sumber
4

Periksa ini sebagai batasan kompiler dasar dan Anda akan mulai bertanya-tanya apa yang terjadi:

int c;
c=1;

switch(c)
{
    case 1:
    //{
        int *i = new int;
        *i =1;
        cout<<*i;
        break;
    //}

    default : cout<<"def";
}

Ini akan memberi Anda kesalahan:

error: jump to case label [-fpermissive]
error:   crosses initialization of int* i

Meskipun yang ini tidak akan:

int c;
c=1;

switch(c)
{
    case 1:
    {
        int *i = new int;
        *i =1;
        cout<<*i;
        break;
    }

    default : cout<<"def";
}
pj
sumber
1

Menggunakan tanda kurung di sakelar menunjukkan blok ruang lingkup baru seperti yang dikatakan oleh Rotem.

Tapi bisa juga untuk kejelasan saat Anda membaca. Untuk mengetahui di mana kasus ini berhenti karena Anda mungkin mengalami break conditionnal di dalamnya.

dyesdyes
sumber
0

Alasannya mungkin:

  1. Keterbacaan, secara visual menyempurnakan setiap kasus sebagai bagian cakupan.
  2. Mendeklarasikan variabel berbeda dengan nama yang sama untuk beberapa kasus sakelar.
  3. Pengoptimalan mikro- ruang lingkup untuk variabel alokasi sumber daya yang sangat mahal yang ingin Anda hancurkan segera setelah Anda meninggalkan ruang lingkup kasus, atau bahkan skenario spageti yang lebih banyak dari penggunaan perintah "GOTO".
Roman Ambinder
sumber