Bagaimana cara keluar dari loop dari dalam sebuah saklar?

113

Saya menulis beberapa kode yang terlihat seperti ini:

while(true) {
    switch(msg->state) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
    case DONE:
        break; // **HERE, I want to break out of the loop itself**
    }
}

Apakah ada cara langsung untuk melakukan itu?

Saya tahu saya bisa menggunakan sebuah bendera, dan keluar dari perulangan dengan meletakkan jeda bersyarat tepat setelah saklar. Saya hanya ingin tahu apakah C ++ sudah memiliki beberapa konstruksi untuk ini.

jrharshath
sumber
20
Mengapa Anda memerlukan jeda bersyarat setelah sakelar? Ubah saja while Anda dari while (true) ke while (flag) ...
Tal Pressman
7
@Dave_Jarvis Saya berasumsi bahwa ini adalah versi sederhana yang dia masukkan di sini untuk menggambarkan apa yang dia coba lakukan.
Alterlife
6
Jika Anda adalah salah satu programmer yang menghasilkan fungsi yang panjangnya beberapa halaman, Anda akan menganggapnya gotomenarik dan, terkadang, satu-satunya jalan keluar yang bersih. Jika Anda cenderung mengatur kode Anda menjadi fungsi-fungsi kecil yang panjangnya hanya beberapa baris dan masing-masing melakukan satu hal, Anda tidak akan pernah mengalami masalah ini. (Kebetulan kode Anda juga akan lebih mudah dibaca.)
sbi
13
Jika merasa ingin mendapatkan nasihat untuk berhenti merokok, padahal yang ingin Anda ketahui hanyalah bagaimana menuju ke stasiun kereta bawah tanah terdekat.
Michael Krelin - peretas
7
@ Hacker: Nah, jika Anda tidak dapat melihat stasiun kereta bawah tanah di depan Anda karena semua asap, saran itu mungkin tidak terlalu buruk. :)
sbi

Jawaban:

49

Premis

Kode berikut harus dianggap bentuk yang buruk, terlepas dari bahasa atau fungsi yang diinginkan:

while( true ) {
}

Argumen Pendukung

Bentuk while( true )loopnya buruk karena:

  • Memutus kontrak tersirat dari loop sementara.
    • Deklarasi while loop harus secara eksplisit menyatakan satu - satunya kondisi keluar.
  • Menyiratkan bahwa loop selamanya.
    • Kode di dalam loop harus dibaca untuk memahami klausa terminating.
    • Loop yang berulang selamanya mencegah pengguna untuk menghentikan program dari dalam program.
  • Tidak efisien.
    • Ada beberapa kondisi penghentian loop, termasuk memeriksa "benar".
  • Rawan bug.
    • Tidak dapat dengan mudah menentukan di mana harus meletakkan kode yang akan selalu dieksekusi untuk setiap iterasi.
  • Menghasilkan kode kompleks yang tidak perlu.
  • Analisis kode sumber otomatis.
    • Untuk menemukan bug, analisis kompleksitas program, pemeriksaan keamanan, atau secara otomatis mendapatkan perilaku kode sumber lainnya tanpa eksekusi kode, menentukan kondisi pemutusan awal memungkinkan algoritme untuk menentukan invarian yang berguna, sehingga meningkatkan metrik analisis kode sumber otomatis.
  • Loop tak terbatas.
    • Jika setiap orang selalu menggunakan while(true)untuk loop yang tidak terbatas, kita kehilangan kemampuan untuk berkomunikasi secara ringkas ketika loop sebenarnya tidak memiliki kondisi penghentian. (Bisa dibilang, ini sudah terjadi, jadi intinya diperdebatkan.)

Alternatif untuk "Pergi ke"

Kode berikut adalah bentuk yang lebih baik:

while( isValidState() ) {
  execute();
}

bool isValidState() {
  return msg->state != DONE;
}

Keuntungan

Tidak ada bendera. Tidak goto. Tanpa pengecualian. Mudah diubah. Mudah dibaca. Mudah diperbaiki. Selain itu kode:

  1. Mengisolasi pengetahuan tentang beban kerja loop dari loop itu sendiri.
  2. Memungkinkan seseorang memelihara kode dengan mudah memperluas fungsinya.
  3. Memungkinkan beberapa kondisi pengakhiran untuk ditugaskan di satu tempat.
  4. Memisahkan klausa penghentian dari kode yang akan dieksekusi.
  5. Lebih aman untuk pembangkit listrik tenaga nuklir. ;-)

Poin kedua penting. Tanpa mengetahui cara kerja kode, jika seseorang meminta saya untuk membuat loop utama, biarkan utas (atau proses) lain memiliki waktu CPU, dua solusi muncul dalam pikiran:

Pilihan 1

Masukkan jeda dengan mudah:

while( isValidState() ) {
  execute();
  sleep();
}

Pilihan 2

Timpa eksekusi:

void execute() {
  super->execute();
  sleep();
}

Kode ini lebih sederhana (sehingga lebih mudah dibaca) daripada loop dengan embedded switch. The isValidStateMetode hanya harus menentukan apakah loop harus terus. Pekerja keras metode ini harus diabstraksikan ke dalam executemetode, yang memungkinkan subclass untuk menimpa perilaku default (tugas yang sulit menggunakan switchdan tertanam goto).

Contoh Python

Bandingkan jawaban berikut (dengan pertanyaan Python) yang diposting di StackOverflow:

  1. Ulangi selamanya.
  2. Minta pengguna untuk memasukkan pilihan mereka.
  3. Jika input pengguna adalah 'restart', lanjutkan looping selamanya.
  4. Jika tidak, hentikan perulangan selamanya.
  5. Akhir.
Kode
while True: 
    choice = raw_input('What do you want? ')

    if choice == 'restart':
        continue
    else:
        break

print 'Break!' 

Melawan:

  1. Inisialisasi pilihan pengguna.
  2. Loop sedangkan pilihan pengguna adalah kata 'restart'.
  3. Minta pengguna untuk memasukkan pilihan mereka.
  4. Akhir.
Kode
choice = 'restart';

while choice == 'restart': 
    choice = raw_input('What do you want? ')

print 'Break!'

Di sini, while Truemenghasilkan kode yang menyesatkan dan terlalu kompleks.

Dave Jarvis
sumber
45
Gagasan yang while(true)semestinya berbahaya tampak aneh bagi saya. Ada loop yang memeriksa sebelum dieksekusi, ada yang memeriksa setelahnya, dan ada yang memeriksa di tengah. Karena yang terakhir tidak memiliki konstruksi sintaksis dalam C dan C ++, Anda harus menggunakan while(true)atau for(;;). Jika Anda menganggap ini salah, Anda belum cukup memikirkan jenis loop yang berbeda.
sbi
34
Alasan mengapa saya tidak setuju: while (true) dan for (;;;) tidak membingungkan (tidak seperti do {} while (true)) karena menyatakan apa yang mereka lakukan di depan. Sebenarnya lebih mudah untuk mencari pernyataan "break" daripada mengurai kondisi loop arbitrer dan mencari di mana ia disetel, dan tidak lebih sulit untuk membuktikan kebenarannya. Argumen tentang di mana harus meletakkan kode sama sulitnya dengan adanya pernyataan lanjutan, dan tidak jauh lebih baik dengan pernyataan if. Argumen efisiensi sangat konyol.
David Thornley
23
Setiap kali Anda melihat "while (true)", Anda dapat memikirkan pengulangan yang memiliki ketentuan penghentian internal, yang tidak lebih sulit dari kondisi akhir arbitrer. Sederhananya, loop "while (foo)", di mana foo adalah variabel boolean yang disetel secara arbitrer, tidak lebih baik daripada "while (true)" dengan jeda. Anda harus melihat kode dalam kedua kasus. Mengemukakan alasan konyol (dan efisiensi mikro adalah argumen konyol) tidak membantu kasus Anda.
David Thornley
5
@ Dave: Saya memberikan alasan: Ini satu-satunya cara untuk mendapatkan loop yang memeriksa kondisi di tengah. Contoh klasik: while(true) {string line; std::getline(is,line); if(!is) break; lines.push_back(line);}Tentu saja saya bisa mengubah ini menjadi loop prakondisi (menggunakan std::getlinesebagai kondisi loop), tetapi ini memiliki kelemahan sendiri ( lineharus menjadi variabel di luar lingkup loop). Dan jika saya melakukannya, saya harus bertanya: Mengapa kita memiliki tiga putaran? Kami selalu dapat mengubah semuanya menjadi loop yang telah ditentukan sebelumnya. Gunakan yang paling cocok. Jika while(true)cocok, maka gunakanlah.
sbi
3
Sebagai rasa hormat kepada orang-orang yang membaca jawaban ini, saya akan menghindari mengomentari kualitas barang yang menghasilkan pertanyaan dan tetap menjawab pertanyaan itu sendiri. Pertanyaannya adalah variasi dari berikut ini, yang saya yakini merupakan fitur dari banyak bahasa: Anda memiliki satu loop yang bersarang di dalam loop lain. Anda ingin keluar dari loop luar dari loop dalam. Bagaimana ini dilakukan?
Alex Leibowitz
172

Anda bisa menggunakan goto.

while ( ... ) {
   switch( ... ) {
     case ...:
         goto exit_loop;

   }
}
exit_loop: ;
Mehrdad Afshari
sumber
12
Jangan berlebihan dengan kata kunci itu.
Fragsworth
45
Lucu sekali saya mendapat suara negatif karena orang tidak suka goto. OP dengan jelas menyebutkan tanpa menggunakan bendera . Bisakah Anda menyarankan cara yang lebih baik, mengingat keterbatasan OP? :)
Mehrdad Afshari
18
suara positif hanya untuk mengkompensasi pembenci goto yang ceroboh. Saya kira Mehrdad tahu dia akan kehilangan beberapa poin karena menyarankan solusi yang masuk akal di sini.
Michael Krelin - hacker
6
1, tidak ada cara yang lebih baik, bahkan ketika mempertimbangkan batasan OP. Bendera jelek, logika putus di seluruh lingkaran dan dengan demikian lebih sulit untuk dipahami.
Johannes Schaub - litb
11
pada akhirnya, tanpa konstruksi goto, semua bahasa gagal. bahasa tingkat tinggi mengaburkannya, tetapi jika Anda melihat cukup keras di bawah tenda, Anda akan menemukan setiap loop, atau kondisi bermuara pada cabang bersyarat dan tidak bersyarat. kita membodohi diri kita sendiri dengan menggunakan for, while, sampai, switch, if keywords, tetapi pada akhirnya mereka semua menggunakan GOTO (atau JMP) dalam satu atau lain cara. Anda bisa menipu diri sendiri, menemukan segala macam cara cerdas untuk menyembunyikannya, atau Anda bisa jujur ​​secara pragmatis, dan menggunakan goto, jika sesuai, dan hemat.
tidak tersinkronisasi
58

Solusi alternatifnya adalah dengan menggunakan kata kunci continuedalam kombinasi dengan break, yaitu:

for (;;) {
    switch(msg->state) {
    case MSGTYPE
        // code
        continue; // continue with loop
    case DONE:
        break;
    }
    break;
}

Gunakan continuepernyataan untuk menyelesaikan setiap label kasus di mana Anda ingin pengulangan melanjutkan dan gunakan breakpernyataan untuk menyelesaikan label kasus yang harus menghentikan pengulangan.

Tentu saja solusi ini hanya berfungsi jika tidak ada kode tambahan untuk dieksekusi setelah pernyataan switch.

sakra
sumber
9
Meskipun ini memang sangat elegan, namun memiliki kelemahan bahwa sebagian besar pengembang harus melihatnya terlebih dahulu sebentar untuk memahami bagaimana / apakah ini bekerja. :(
sbi
8
Kalimat terakhir adalah apa yang membuat ini tidak begitu hebat. :(Jika tidak, implementasi yang sangat bersih.
nawfal
Jika dipikir-pikir, tidak ada kode komputer yang baik. Kami hanya dilatih untuk memahaminya. Misalnya xml tampak seperti sampah sampai saya mempelajarinya. Sekarang tidak lagi mencari sampah. untuk (int x = 0; x <10; ++ x, moveCursor (x, y)) sebenarnya menyebabkan kesalahan logika dalam program saya. Saya lupa bahwa kondisi update terjadi di akhir.
22

Cara yang rapi untuk melakukan ini adalah dengan memasukkan ini ke dalam sebuah fungsi:

int yourfunc() {

    while(true) {

        switch(msg->state) {
        case MSGTYPE: // ... 
            break;
        // ... more stuff ...
        case DONE:
            return; 
        }

    }
}

Opsional (tapi 'praktik buruk'): seperti yang sudah disarankan, Anda bisa menggunakan goto, atau melempar pengecualian di dalam sakelar.

Alterlife
sumber
4
Pengecualian harus digunakan untuk, yah, melempar pengecualian, bukan perilaku yang diketahui dengan baik dari suatu fungsi.
Clement Herreman
Saya setuju dengan Afterlife: Taruh dalam sebuah fungsi.
dalle
14
Melempar pengecualian sangatlah jahat dalam kasus ini. Artinya "Saya ingin menggunakan goto, tapi saya membaca di suatu tempat bahwa saya tidak boleh menggunakannya, jadi saya akan pergi dengan akal-akalan dan berpura-pura terlihat pintar".
Gorpik
Saya setuju bahwa memberikan pengecualian atau menggunakan goto adalah ide yang buruk, tetapi opsi tersebut berfungsi dan telah terdaftar seperti itu dalam jawaban saya.
Alterlife
2
Melempar pengecualian berarti mengganti COMEFROM untuk GOTO. Jangan lakukan itu. Goto bekerja jauh lebih baik.
David Thornley
14

AFAIK tidak ada "istirahat ganda" atau konstruksi serupa di C ++. Yang paling dekat adalah a goto- yang, meskipun memiliki konotasi buruk untuk namanya, ada dalam bahasa karena suatu alasan - selama digunakan dengan hati-hati dan hemat, itu adalah opsi yang layak.

Amber
sumber
8

Anda dapat meletakkan sakelar Anda ke fungsi terpisah seperti ini:

bool myswitchfunction()
{
    switch(msg->state) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
    case DONE:
        return false; // **HERE, I want to break out of the loop itself**
    }
    return true;
}

while(myswitchfunction())
    ;
Adam Pierce
sumber
7

Bahkan jika Anda tidak suka goto, jangan gunakan pengecualian untuk keluar dari loop. Contoh berikut menunjukkan betapa jeleknya itu:

try {
  while ( ... ) {
    switch( ... ) {
      case ...:
        throw 777; // I'm afraid of goto
     }
  }
}
catch ( int )
{
}

Saya akan menggunakan gotoseperti dalam jawaban ini . Dalam hal ini gotoakan membuat kode lebih jelas daripada opsi lainnya. Saya berharap bahwa ini pertanyaan akan membantu.

Tapi saya pikir menggunakan gotoadalah satu-satunya pilihan di sini karena stringnya while(true). Anda harus mempertimbangkan refactoring loop Anda. Saya kira solusi berikut:

bool end_loop = false;
while ( !end_loop ) {
    switch( msg->state ) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
    case DONE:
        end_loop = true; break;
    }
}

Atau bahkan yang berikut ini:

while ( msg->state != DONE ) {
    switch( msg->state ) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
}
Kirill V. Lyadvinsky
sumber
1
Ya, tapi saya lebih suka pengecualian ;-)
quamrana
3
Menggunakan pengecualian untuk meniru goto tentu saja lebih buruk daripada sebenarnya menggunakan goto! Ini mungkin juga akan jauh lebih lambat.
John Carter
2
@ Downvoter: Itu karena saya menyatakan, bahwa Anda tidak boleh menggunakan pengecualian, atau karena saya menyarankan refactoring?
Kirill V. Lyadvinsky
1
Bukan pemilih bawah - tapi ... Jawaban Anda tidak jelas apakah Anda mengusulkan atau mengutuk gagasan menggunakan pengecualian untuk keluar dari lingkaran. Mungkin kalimat pertama harus: "Bahkan jika Anda tidak suka goto, jangan tidak menggunakan pengecualian untuk keluar loop:"?
Jonathan Leffler
1
@Jonathan Leffler, Terima kasih, Anda menunjukkan contoh komentar yang berharga. memperbarui jawaban dengan mengingat komentar Anda.
Kirill V. Lyadvinsky
5

Tidak ada konstruksi C ++ untuk mendobrak loop dalam kasus ini.

Gunakan flag untuk menghentikan loop atau (jika sesuai) ekstrak kode Anda ke dalam fungsi dan gunakan return.

gigi tajam
sumber
2

Anda berpotensi menggunakan goto, tetapi saya lebih suka menyetel bendera yang menghentikan pengulangan. Kemudian keluar dari sakelar.

Martin York
sumber
2

Mengapa tidak memperbaiki kondisi di while loop Anda, menyebabkan masalah hilang?

while(msg->state != DONE)
{
    switch(msg->state) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
    case DONE:
        // We can't get here, but for completeness we list it.
        break; // **HERE, I want to break out of the loop itself**
    }
}
Mark B
sumber
2

Tidak, C ++ tidak memiliki konstruksi untuk ini, mengingat kata kunci "break" sudah disediakan untuk keluar dari blok switch. Alternatifnya, do.. while () dengan flag exit sudah cukup.

do { 
    switch(option){
        case 1: ..; break;
        ...
        case n: .. ;break;
        default: flag = false; break;
    }
} while(flag);
Prashanth Sriram
sumber
1

Kupikir;

while(msg->state != mExit) 
{
    switch(msg->state) 
    {
      case MSGTYPE: // ...
         break;
      case DONE:
      //  .. 
      //  ..
      msg->state =mExit;
      break;
    }
}
if (msg->state ==mExit)
     msg->state =DONE;
ercan
sumber
0

Cara termudah untuk melakukannya adalah dengan meletakkan IF sederhana sebelum Anda melakukan SWITCH, dan IF menguji kondisi Anda untuk keluar dari loop ......... sesederhana mungkin

SpiriAd
sumber
2
Um ... Anda bisa lebih sederhana lagi dengan meletakkan kondisi itu dalam argumen while. Maksud saya untuk itulah itu ada! :)
Mark A. Donohoe
0

Kata breakkunci dalam C ++ hanya mengakhiri iterasi atau switchpernyataan yang paling bertingkat . Jadi, Anda tidak bisa keluar dari while (true)loop langsung di dalam switchpernyataan; namun Anda dapat menggunakan kode berikut, yang menurut saya merupakan pola yang sangat baik untuk jenis masalah ini:

for (; msg->state != DONE; msg = next_message()) {
    switch (msg->state) {
    case MSGTYPE:
        //...
        break;

    //...
    }
}

Jika Anda perlu melakukan sesuatu saat msg->statesama DONE(seperti menjalankan rutinitas pembersihan), letakkan kode tersebut segera setelah forpengulangan; yaitu jika saat ini Anda memiliki:

while (true) {
    switch (msg->state) {
    case MSGTYPE:
        //... 
        break;

    //...

    case DONE:
        do_cleanup();
        break;
    }

    if (msg->state == DONE)
        break;

    msg = next_message();
}

Kemudian gunakan sebagai gantinya:

for (; msg->state != DONE; msg = next_message()) {
    switch (msg->state) {
    case MSGTYPE:
        //...
        break;

    //...
    }
}

assert(msg->state == DONE);
do_cleanup();
Daniel Trebbien
sumber
0

Sungguh mengherankan saya betapa sederhananya ini mengingat kedalaman penjelasan ... Inilah yang Anda butuhkan ...

bool imLoopin = true;

while(imLoopin) {

    switch(msg->state) {

        case MSGTYPE: // ... 
            break;

        // ... more stuff ...

        case DONE:
            imLoopin = false;
            break;

    }

}

LOL !! Betulkah! Hanya itu yang Anda butuhkan! Satu variabel ekstra!

Mark A. Donohoe
sumber
0
while(MyCondition) {
switch(msg->state) {
case MSGTYPE: // ... 
    break;
// ... more stuff ...
case DONE:
   MyCondition=false; // just add this code and you will be out of loop.
    break; // **HERE, you want to break out of the loop itself**
}
}
Wajeed-MSFT
sumber
0

Saya mendapat masalah yang sama dan diselesaikan dengan menggunakan bendera.

bool flag = false;
while(true) {
    switch(msg->state) {
    case MSGTYPE: // ... 
        break;
    // ... more stuff ...
    case DONE:
        flag = true; // **HERE, I want to break out of the loop itself**
    }
    if(flag) break;
}
Ram Suthar
sumber
-2

Jika saya mengingat sintaks C ++ dengan baik, Anda dapat menambahkan label ke breakpernyataan, seperti untuk goto. Jadi apa yang Anda inginkan akan mudah ditulis:

while(true) {
    switch(msg->state) {
    case MSGTYPE: // ...
        break;
    // ... more stuff ...
    case DONE:
        break outofloop; // **HERE, I want to break out of the loop itself**
    }
}

outofloop:
// rest of your code here
Andrei Vajna II
sumber
1
Sayangnya, ingatan Anda salah. Ini akan menjadi fitur yang bagus, pada kesempatan langka yang akan berguna. (Saya telah melihat proposal untuk sejumlah level untuk ditembus, tetapi bagi saya itu tampak seperti bug yang menunggu untuk terjadi.)
David Thornley
Itu pasti Java. Terimakasih telah menjawab. Dan, tentu saja, Anda juga setuju dengan yang lainnya.
Andrei Vajna II
-3
  while(true)
  {
    switch(x)
    {
     case 1:
     {
      break;
     }
    break;
   case 2:
    //some code here
   break;
  default:
  //some code here
  }
}
Chevaughn
sumber
1
Semua jeda dalam contoh kode itu keluar dari pernyataan sakelar.
Dan Hulme