Kenapa kita harus menggunakan break in switch?

74

Siapa yang memutuskan (dan berdasarkan konsep apa) yang harus digunakan switchkonstruksi (dalam banyak bahasa) breakdi setiap pernyataan?

Mengapa kita harus menulis sesuatu seperti ini:

switch(a)
{
    case 1:
        result = 'one';
        break;
    case 2:
        result = 'two';
        break;
    default:
        result = 'not determined';
        break;
}

(perhatikan ini di PHP dan JS; mungkin ada banyak bahasa lain yang menggunakan ini)

Jika switchmerupakan alternatif if, mengapa kita tidak dapat menggunakan konstruksi yang sama seperti untuk if? Yaitu:

switch(a)
{
    case 1:
    {
        result = 'one';
    }
    case 2:
    {
        result = 'two';
    }
    default:
    {
        result = 'not determined';
    }
}

Dikatakan bahwa breakmencegah eksekusi blok mengikuti yang sekarang. Tapi, apakah seseorang benar-benar mengalami situasi, di mana ada kebutuhan untuk eksekusi blok saat ini dan yang berikutnya? Saya tidak melakukannya. Bagi saya, breakselalu ada. Di setiap blok. Di setiap kode.

Trejder
sumber
1
Seperti sebagian besar jawaban telah dicatat, ini terkait dengan 'fall-through', di mana beberapa kondisi dialihkan melalui blok 'then' yang sama. Namun, ini tergantung pada bahasa - RPG, misalnya, memperlakukan CASEpernyataan yang setara dengan raksasa jika / elseif diblokir.
Clockwork-Muse
34
Itu adalah keputusan yang buruk yang dibuat oleh desainer-C (seperti banyak keputusan, itu dibuat untuk membuat transisi dari perakitan -> C, dan terjemahannya kembali, lebih mudah, daripada untuk kemudahan pemrograman) , yang sayangnya diwariskan dalam bahasa berbasis C lainnya. Menggunakan aturan sederhana "Selalu buat kasus umum menjadi default !!" , kita dapat melihat bahwa perilaku default seharusnya adalah break, dengan kata kunci terpisah untuk gagal , karena itu adalah kasus yang paling umum.
BlueRaja - Danny Pflughoeft
4
Saya telah menggunakan fall-through pada beberapa kesempatan, meskipun bisa dibilang pernyataan beralih dalam bahasa pilihan saya pada saat itu (C) dapat dirancang untuk menghindarinya; misalnya saya telah melakukan case 'a': case 'A': case 'b': case 'B'tetapi sebagian besar karena saya tidak bisa melakukannya case in [ 'a', 'A', 'b', 'B' ]. Pertanyaan yang sedikit lebih baik adalah, dalam bahasa pilihan saya saat ini (C #), istirahat wajib , dan tidak ada fall-implisit; Lupa breakadalah kesalahan sintaksis ...: \
KutuluMike
1
Gagal dengan kode aktual sering ditemukan dalam implementasi parser. case TOKEN_A: /*set flag*/; case TOKEN_B: /*consume token*/; break; case TOKEN_C: /*...*/
Stop Harming Monica
1
@ BlueRaja-DannyPflughoeft: C juga dirancang agar mudah dikompilasi. “Memancarkan lompatan jika breakada di mana saja” adalah aturan yang jauh lebih sederhana untuk diterapkan daripada “Jangan memancarkan lompatan jika fallthroughada dalam switch”.
Jon Purdy

Jawaban:

95

C adalah salah satu bahasa pertama yang memiliki switchpernyataan dalam bentuk ini, dan semua bahasa utama lainnya mewarisinya dari sana, sebagian besar memilih untuk mempertahankan semantik C per default - mereka juga tidak memikirkan keuntungan mengubahnya, atau menilai mereka kurang penting daripada menjaga perilaku semua orang dulu.

Adapun mengapa C dirancang seperti itu, mungkin berasal dari konsep C sebagai "perakitan portabel". The switchpernyataan pada dasarnya adalah sebuah abstraksi dari meja cabang , dan meja cabang juga memiliki implisit jatuh-melalui dan membutuhkan instruksi jump tambahan untuk menghindarinya.

Jadi pada dasarnya, para perancang C juga memilih untuk menjaga semantik assembler per default.

Michael Borgwardt
sumber
3
VB.NET adalah contoh bahasa yang mengubahnya. Tidak ada bahaya jika itu mengalir ke klausa kasus.
colek
1
Tcl adalah bahasa lain yang tidak pernah masuk ke klausa berikutnya. Hampir tidak pernah ingin melakukannya juga (tidak seperti di C di mana itu berguna saat menulis jenis program tertentu).
Donal Fellows
4
Bahasa leluhur C, B dan BCPL yang lebih tua , keduanya memiliki (memiliki?) Pernyataan peralihan; BCPL mengejanya switchon.
Keith Thompson
Pernyataan kasus Ruby tidak jatuh; Anda bisa mendapatkan perilaku serupa dengan memiliki dua nilai tes dalam satu when.
Nathan Long
86

Karena switchbukan merupakan alternatif if ... elsepernyataan dalam bahasa-bahasa tersebut.

Dengan menggunakan switch, kami dapat mencocokkan lebih dari satu kondisi pada satu waktu, yang sangat dihargai dalam beberapa kasus.

Contoh:

public Season SeasonFromMonth(Month month)
{
    Season season;
    switch (month)
    {
        case Month.December:
        case Month.January:
        case Month.February:
            season = Season.Winter;
            break;

        case Month.March:
        case Month.April:
        case Month.May:
            season = Season.Spring;
            break;

        case Month.June:
        case Month.July:
        case Month.August:
            season = Season.Summer;
            break;

        default:
            season = Season.Autumn;
            break;
    }

    return season;
}
Satish Pandey
sumber
15
more than one block ... unwanted in most casesSaya tidak setuju dengan Anda.
Satish Pandey
2
@DanielB Tergantung pada bahasa; Pernyataan pemindahan C # tidak memungkinkan untuk kemungkinan itu.
Andy
5
@Andy Apakah kita masih berbicara tentang kejatuhan? Pernyataan peralihan C # benar-benar memungkinkan untuk itu (periksa contoh ke-3), karena itu diperlukan break. Tapi ya, sejauh yang saya ketahui, Perangkat Duff tidak berfungsi di C #.
Daniel B
8
@ DanielBB Ya kita, tetapi kompiler tidak akan mengizinkan suatu kondisi, kode, dan kemudian kondisi lain tanpa istirahat. Anda dapat memiliki banyak kondisi yang ditumpuk tanpa kode di antaranya, atau Anda perlu istirahat. Dari tautan Anda C# does not support an implicit fall through from one case label to another. Anda bisa jatuh, tetapi tidak ada kesempatan untuk secara tidak sengaja melupakan istirahat.
Andy
3
@ Matemann .. Saya sekarang kita bisa melakukan semua hal menggunakan if..elsetetapi dalam beberapa situasi switch..caseakan lebih baik. Contoh di atas akan sedikit rumit jika kita menggunakan if-else.
Satish Pandey
15

Ini telah ditanyakan pada Stack Overflow dalam konteks C: Mengapa pernyataan switch dirancang untuk membutuhkan istirahat?

Untuk meringkas jawaban yang diterima, itu mungkin sebuah kesalahan. Sebagian besar bahasa lain mungkin baru saja mengikuti C. Namun, beberapa bahasa seperti C # tampaknya telah memperbaikinya dengan membiarkan fall-through - tetapi hanya ketika programmer secara eksplisit mengatakannya (sumber: tautan di atas, saya tidak berbicara C # sendiri) .

Tapio
sumber
Google Go melakukan hal yang sama IIRC (memungkinkan fallthrough jika ditentukan secara eksplisit).
Leo
PHP juga. Dan semakin Anda melihat kode yang dihasilkan, semakin tidak enak rasanya membuat Anda merasa. Saya suka /* FALLTHRU */komentar dalam jawaban yang ditautkan oleh @Tapio di atas, untuk membuktikan bahwa Anda bersungguh-sungguh.
DaveP
1
Mengatakan C # memiliki goto case, seperti tautannya, tidak menggambarkan mengapa itu bekerja dengan baik. Anda masih dapat menumpuk banyak kasus seperti di atas, tetapi segera setelah Anda memasukkan kode, Anda harus memiliki eksplisit goto case whateveruntuk masuk ke kasus berikutnya atau Anda mendapatkan eror kompiler. Jika Anda bertanya kepada saya, dari keduanya, kemampuan untuk menumpuk kasus tanpa khawatir tentang kelalaian yang terjadi secara tidak sengaja adalah fitur yang jauh lebih baik daripada memungkinkan kegagalan secara eksplisit goto case.
Jesper
9

Saya akan menjawab dengan sebuah contoh. Jika Anda ingin membuat daftar jumlah hari untuk setiap bulan dalam setahun, jelas bahwa beberapa bulan memiliki 31, sekitar 30, dan 1 28/29. Akan terlihat seperti ini,

switch(month) {
    case 4:
    case 6:
    case 9:
    case 11;
        days = 30;
        break;
    case 2:
        //Find out if is leap year( divisible by 4 and all that other stuff)
        days = 28 or 29;
        break;
    default:
        days = 31;
}

Ini adalah contoh di mana banyak kasus memiliki efek yang sama dan semuanya dikelompokkan bersama. Jelas ada alasan untuk pilihan kata kunci break dan bukan if ... else if construct.

Hal utama yang perlu diperhatikan di sini adalah bahwa pernyataan peralihan dengan banyak kasus serupa bukan jika ... atau jika ... lain jika ... lain untuk masing-masing kasus, melainkan jika (1, 2, 3) ... else if (4,5,6) else ...

Awemo
sumber
2
Misalnya Anda terlihat lebih verbose daripada iftanpa manfaat apapun. Mungkin jika contoh Anda menetapkan nama atau apakah pekerjaan spesifik bulan sebagai tambahan dari fall-through, utilitas fall-through akan lebih jelas? (tanpa mengomentari apakah seseorang HARUS di tempat pertama)
horatio
1
Itu benar. Namun saya hanya berusaha menunjukkan kasus-kasus di mana kejatuhan dapat digunakan. Jelas akan jauh lebih baik jika setiap bulan membutuhkan sesuatu yang dilakukan secara individual.
Awemo
8

Ada dua situasi di mana "jatuh" dari satu kasus ke yang lain dapat terjadi - kasus kosong:

switch ( ... )
{
  case 1:
  case 2:
    do_something_for_1_and_2();
    break;
  ...
}

dan kasing kosong

switch ( ... )
{
  case 1:
    do_something_for_1();
    /* Deliberately fall through */
  case 2:
    do_something_for_1_and_2();
    break;
...
}

Terlepas dari rujukan ke Perangkat Duff , contoh yang sah untuk kasus kedua sedikit dan jarang, dan umumnya dilarang dengan standar pengkodean dan ditandai selama analisis statis. Dan di mana ditemukan, itu lebih sering daripada tidak karena kelalaian a break.

Yang pertama sangat masuk akal dan umum.

Sejujurnya, saya tidak dapat melihat alasan untuk membutuhkan breakdan parser bahasa mengetahui bahwa tubuh kasus kosong sedang jatuh, sementara kasus tidak kosong adalah mandiri.

Sangat disayangkan bahwa panel ISO C tampaknya lebih sibuk dengan menambahkan fitur-fitur baru (yang tidak diinginkan) ke bahasa, daripada memperbaiki fitur yang tidak ditentukan, tidak ditentukan atau implementasi-didefinisikan, belum lagi yang tidak logis.

Andrew
sumber
2
Syukurlah ISO C tidak memperkenalkan perubahan yang melanggar ke dalam bahasa!
Jack Aidley
4

Tidak memaksa breakmemungkinkan sejumlah hal yang sebaliknya bisa sulit dilakukan. Yang lain telah mencatat kasus pengelompokan, di mana ada sejumlah kasus yang tidak sepele.

Satu kasus di mana sangat penting bahwa breaktidak boleh digunakan adalah Perangkat Duff . Ini digunakan untuk " membuka gulungan " loop di mana ia dapat mempercepat operasi dengan membatasi jumlah perbandingan yang diperlukan. Saya percaya penggunaan awal memungkinkan fungsionalitas yang sebelumnya terlalu lambat dengan loop yang digulung penuh. Itu perdagangan ukuran kode untuk kecepatan dalam beberapa kasus.

Merupakan praktik yang baik untuk mengganti breakdengan komentar yang sesuai, jika kasing memiliki kode. Jika tidak, seseorang akan memperbaiki yang hilang break, dan memperkenalkan bug.

BillThor
sumber
Terima kasih atas contoh Perangkat Duff (+1). Aku tidak menyadari itu sebelumnya.
trejder
3

Di C, di mana asal tampaknya, blok kode switchpernyataan itu bukan konstruksi khusus. Ini adalah blok kode yang normal, seperti halnya blok di bawah ifpernyataan.

switch ()
{
}

if ()
{
}

casedan defaultmelompati label di dalam blok ini, khususnya terkait dengan switch. Mereka ditangani sama seperti label lompat biasa goto. Ada satu aturan khusus yang penting di sini: Label lompat dapat hampir di mana-mana dalam kode, tanpa mengganggu aliran kode.

Sebagai blok kode normal, itu tidak harus berupa pernyataan majemuk. Label juga opsional. Ini adalah switchpernyataan yang valid dalam C:

switch (a)
    case 1: Foo();

switch (a)
    Foo();

switch (a)
{
    Foo();
}

Standar C sendiri memberikan ini sebagai contoh (6.8.4.2):

switch (expr) 
{ 
    int i = 4; 
    f(i); 
  case 0: 
    i=17; 
    /*falls through into default code */ 
  default: 
    printf("%d\n", i); 
} 

Dalam fragmen program buatan, objek yang pengidentifikasinya ada dengan durasi penyimpanan otomatis (di dalam blok) tetapi tidak pernah diinisialisasi, dan dengan demikian jika ekspresi kontrol memiliki nilai bukan nol, panggilan ke fungsi printf akan mengakses nilai yang tidak ditentukan. Demikian pula, panggilan ke fungsi f tidak dapat dihubungi.

Selain itu, defaultlabel lompat juga, dan dengan demikian bisa di mana saja, tanpa perlu menjadi kasus terakhir.

Ini juga menjelaskan Perangkat Duff:

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

Mengapa jatuh? Karena dalam aliran kode normal dalam blok kode normal , diharapkan untuk menjalankan pernyataan berikutnya , seperti yang Anda harapkan dalam ifblok kode.

if (a == b)
{
    Foo();
    /* "Fall-through" to Bar expected here. */
    Bar();
}

switch (a)
{
    case 1: 
        Foo();
        /* Automatic break would violate expected code execution semantics. */
    case 2:
        Bar();
}

Dugaan saya adalah bahwa alasan untuk ini adalah kemudahan implementasi. Anda tidak perlu kode khusus untuk switchmem -parsing dan menyusun blok, merawat aturan khusus. Anda cukup menguraikannya seperti kode lain dan hanya perlu merawat label dan pemilihan lompat.

Pertanyaan tindak lanjut yang menarik dari semua ini adalah apakah pernyataan bersarang berikut mencetak "Selesai." atau tidak.

int a = 10;

switch (a)
{
    switch (a)
    {
        case 10: printf("Done.\n");
    }
}

Standar C memperhatikan hal ini (6.8.4.2.4):

Kasing atau label default hanya dapat diakses dalam pernyataan sakelar penutup terlampir.

Aman
sumber
1

Beberapa orang telah menyebutkan gagasan mencocokkan berbagai kondisi, yang sangat berharga dari waktu ke waktu. Namun kemampuan untuk mencocokkan beberapa kondisi tidak selalu mengharuskan Anda melakukan hal yang sama persis dengan setiap kondisi yang cocok. Pertimbangkan yang berikut ini:

switch (someCase)
{
    case 1:
    case 2:
        doSomething1And2();
        break;

    case 3:
        doSomething3();

    case 4:
        doSomething3And4();
        break;

    default:
        throw new Error("Invalid Case");
}

Ada dua cara yang berbeda set beberapa kondisi dicocokkan di sini. Dengan kondisi 1 dan 2, mereka hanya jatuh ke plot kode yang sama persis dan melakukan hal yang sama persis. Namun dengan kondisi 3 dan 4, meskipun keduanya berakhir dengan panggilan doSomething3And4(), hanya 3 panggilan doSomething3().

Panzercrisis
sumber
Terlambat ke pesta, tapi ini sama sekali bukan "1 dan 2" seperti nama fungsi menyarankan: ada tiga negara yang berbeda yang semuanya dapat mengarah pada kode dalam case 2memicu, jadi jika kita ingin menjadi benar (dan kita lakukan) yang asli nama fungsi harus berupa doSomethingBecause_1_OR_2_OR_1AND2()atau sesuatu (walaupun kode yang sah di mana sebuah immutable cocok dengan dua kasus yang berbeda saat masih menjadi kode yang ditulis dengan baik hampir tidak ada, jadi setidaknya ini seharusnya doSomethingBecause1or2())
Mike 'Pomax' Kamermans
Dengan kata lain doSomething[ThatShouldBeDoneInTheCaseOf]1And[InTheCaseOf]2(),. Itu tidak mengacu pada logis dan / atau.
Panzercrisis
Saya tahu itu tidak, tetapi mengingat mengapa orang menjadi bingung dengan kode semacam ini, itu harus menjelaskan apa itu "dan", karena mengandalkan seseorang untuk menebak "akhir alami dan" vs "akhir logis" dalam jawaban yang hanya berfungsi ketika dijelaskan dengan tepat, perlu lebih banyak penjelasan dalam jawaban itu sendiri, atau penulisan ulang nama fungsi untuk menghapus ambiguitas itu =)
Mike 'Pomax' Kamermans
1

Untuk menjawab dua pertanyaan Anda.

Mengapa C perlu istirahat?

Ia datang ke akar Cs sebagai "assembler portabel". Di mana kode psudo seperti ini umum: -

    targets=(addr1,addr2,addr3);
    opt = 1  ## or 0 or 2
switch:
    br targets[opt]  ## go to addr2 
addr1:
    do 1stuff
    br switchend
addr2:
    do 2stuff
    br switchend
addr3
    do 3stuff
switchend:
    ......

pernyataan sakelar dirancang untuk menyediakan fungsionalitas serupa di tingkat yang lebih tinggi.

Apakah kita pernah memiliki sakelar tanpa putus?

Ya ini sangat umum dan ada beberapa kasus penggunaan;

Pertama, Anda mungkin ingin mengambil tindakan yang sama untuk beberapa kasus. Kami melakukan ini dengan menumpuk kasus di atas satu sama lain:

case 6:
case 9:
    // six or nine code

Kasus penggunaan lain yang umum di mesin negara adalah bahwa setelah memproses satu negara kami segera ingin masuk dan memproses negara lain:

case 9:
    crashed()
    newstate=10;
case 10:
    claim_damage();
    break;
James Anderson
sumber
-2

Pernyataan break adalah pernyataan melompat yang memungkinkan pengguna untuk keluar dari saklar penutup terdekat (untuk kasus Anda), sementara, lakukan, untuk atau foreach. itu semudah itu.

jasper
sumber
-3

Saya pikir dengan semua yang tertulis di atas - hasil utama utas ini adalah bahwa jika Anda ingin merancang bahasa baru - defaultnya adalah Anda tidak perlu menambahkan breakpernyataan dan kompiler akan memperlakukannya seperti jika Anda melakukannya.

Jika Anda ingin kasus langka di mana Anda ingin melanjutkan ke kasus berikutnya - cukup nyatakan dengan continuepernyataan.

Ini dapat ditingkatkan sehingga hanya jika Anda menggunakan kurung keriting di dalam case maka itu tidak berjalan sehingga contoh dengan bulan-bulan di atas dari beberapa kasus melakukan kode persis sama - akan selalu bekerja seperti yang diharapkan tanpa memerlukan continue.

etans
sumber
2
Saya tidak yakin saya memahami saran Anda relatif terhadap pernyataan lanjutan. Lanjutkan memiliki arti yang sangat berbeda dalam C dan C ++ dan jika bahasa baru seperti C, tetapi kadang-kadang menggunakan kata kunci seperti dengan C dan kadang-kadang dengan cara alternatif ini, saya pikir kebingungan akan memerintah. Meskipun mungkin ada beberapa masalah dengan jatuh dari satu blok berlabel ke yang lain, saya suka penggunaan beberapa kasus dengan penanganan yang sama dengan blok kode bersama.
Pengembang
C memiliki tradisi panjang dalam menggunakan kata kunci yang sama untuk berbagai keperluan. Pikirkan *, &, staticdll
idrougge