Làm thế nào để thoát khỏi vòng lặp từ bên trong một công tắc?


113

Tôi đang viết một số mã trông giống như sau:

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

Có cách nào trực tiếp để làm điều đó?

Tôi biết mình có thể sử dụng cờ và ngắt khỏi vòng lặp bằng cách đặt dấu ngắt có điều kiện ngay sau nút chuyển. Tôi chỉ muốn biết nếu C ++ có một số cấu trúc cho điều này đã.


20
Tại sao bạn cần nghỉ có điều kiện sau khi chuyển đổi? Chỉ cần thay đổi khi bạn từ while (true) để khi (cờ) ...
Tal Pressman

7
@Dave_Jarvis Tôi cho rằng đây là một phiên bản đơn giản hóa mà anh ấy đã đưa vào đây để minh họa những gì anh ấy đang cố gắng làm.
Alterlife 14/09/09

6
Nếu bạn là một trong những lập trình viên tạo ra các hàm dài vài trang, bạn sẽ thấy gotohấp dẫn và đôi khi là lối thoát duy nhất. Nếu bạn có xu hướng sắp xếp mã của mình thành các chức năng nhỏ chỉ dài vài dòng và làm một việc duy nhất, bạn sẽ không bao giờ gặp phải vấn đề này. (Tình cờ là mã của bạn cũng sẽ dễ đọc hơn.)
sbi 14/09/09

13
Nếu bạn cảm thấy muốn nhận được lời khuyên từ bỏ thuốc lá khi tất cả những gì bạn muốn biết là cách đến ga tàu điện ngầm gần nhất.
Michael Krelin - hacker 14/09/09

7
@hacker: Chà, nếu bạn không thể nhìn thấy ga tàu điện ngầm trước mặt do toàn khói, thì lời khuyên đó có thể không tệ như vậy. :)
sbi 15/09/09

Câu trả lời:


49

Tiền đề

Mã sau đây sẽ được coi là hình thức xấu, bất kể ngôn ngữ hoặc chức năng mong muốn:

while( true ) {
}

Lập luận hỗ trợ

Các while( true )vòng lặp là hình thức nghèo vì nó:

  • Phá vỡ hợp đồng ngụ ý của một vòng lặp trong khi.
    • Khai báo vòng lặp while phải nêu rõ điều kiện thoát duy nhất .
  • Ngụ ý rằng nó lặp lại mãi mãi.
    • Mã trong vòng lặp phải được đọc để hiểu mệnh đề kết thúc.
    • Các vòng lặp lặp lại mãi mãi ngăn người dùng chấm dứt chương trình từ bên trong chương trình.
  • Không hiệu quả.
    • Có nhiều điều kiện kết thúc vòng lặp, bao gồm cả việc kiểm tra "true".
  • Dễ bị lỗi.
    • Không thể dễ dàng xác định nơi đặt mã sẽ luôn thực thi cho mỗi lần lặp.
  • Dẫn đến mã phức tạp không cần thiết.
  • Phân tích mã nguồn tự động.
    • Để tìm lỗi, phân tích độ phức tạp của chương trình, kiểm tra bảo mật hoặc tự động lấy ra bất kỳ hành vi mã nguồn nào khác mà không cần thực thi mã, việc chỉ định (các) điều kiện phá vỡ ban đầu cho phép các thuật toán xác định các bất biến hữu ích, do đó cải thiện các chỉ số phân tích mã nguồn tự động.
  • Vòng lặp vô hạn.
    • Nếu mọi người luôn sử dụng while(true)vòng lặp for không phải là vô hạn, chúng ta sẽ mất khả năng giao tiếp chính xác khi các vòng lặp thực sự không có điều kiện kết thúc. (Có thể cho rằng, điều này đã xảy ra rồi, vì vậy vấn đề là tranh luận.)

Thay thế cho "Đi tới"

Đoạn mã sau là dạng tốt hơn:

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

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

Ưu điểm

Không có cờ. Không goto. Không có ngoại lệ. Dễ dàng thay đổi. Dễ đọc. Dễ dàng sửa chữa. Ngoài ra, mã:

  1. Cô lập kiến ​​thức về khối lượng công việc của vòng lặp khỏi chính vòng lặp.
  2. Cho phép ai đó duy trì mã dễ dàng mở rộng chức năng.
  3. Cho phép nhiều điều kiện kết thúc được gán ở một nơi.
  4. Tách mệnh đề kết thúc khỏi mã để thực thi.
  5. An toàn hơn cho các nhà máy Điện hạt nhân. ;-)

Điểm thứ hai là quan trọng. Nếu không biết mã hoạt động như thế nào, nếu ai đó yêu cầu tôi tạo vòng lặp chính để các luồng (hoặc quy trình) khác có một chút thời gian CPU, bạn sẽ nghĩ đến hai giải pháp:

Lựa chọn 1

Dễ dàng chèn tạm dừng:

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

Lựa chọn 2

Ghi đè thực thi:

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

Mã này đơn giản hơn (do đó dễ đọc hơn) so với một vòng lặp có nhúng switch. Các isValidStatephương pháp duy nhất nên xác định xem vòng lặp nên tiếp tục. Workhorse của phương thức nên được trừu tượng hóa thành executephương thức, điều này cho phép các lớp con ghi đè hành vi mặc định (một nhiệm vụ khó khăn khi sử dụng nhúng switchgoto).

Ví dụ Python

Đối chiếu câu trả lời sau (cho một câu hỏi Python) đã được đăng trên StackOverflow:

  1. Vòng lặp mãi mãi.
  2. Yêu cầu người dùng nhập lựa chọn của họ.
  3. Nếu đầu vào của người dùng là 'khởi động lại', hãy tiếp tục lặp lại mãi mãi.
  4. Nếu không, dừng lặp lại mãi mãi.
  5. Kết thúc.
while True: 
    choice = raw_input('What do you want? ')

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

print 'Break!' 

Đấu với:

  1. Khởi tạo sự lựa chọn của người dùng.
  2. Vòng lặp trong khi lựa chọn của người dùng là từ 'khởi động lại'.
  3. Yêu cầu người dùng nhập lựa chọn của họ.
  4. Kết thúc.
choice = 'restart';

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

print 'Break!'

Ở đây, while Truedẫn đến mã sai lệch và quá phức tạp.


45
Ý tưởng while(true)có hại cho tôi có vẻ kỳ lạ. Có những vòng kiểm tra trước khi chúng thực thi, có những vòng kiểm tra sau đó và có những vòng kiểm tra ở giữa. Vì sau này không có cấu trúc cú pháp trong C và C ++, bạn sẽ phải sử dụng while(true)hoặc for(;;). Nếu bạn coi điều này là sai, bạn vẫn chưa suy nghĩ đủ về các loại vòng lặp khác nhau.
sbi 14/09/09

34
Lý do khiến tôi không đồng ý: while (true) và for (;;;) không gây nhầm lẫn (không giống như do {} while (true)) vì chúng nêu rõ những gì họ đang làm ngay trước mắt. Việc tìm kiếm các câu lệnh "break" thực sự dễ dàng hơn là phân tích cú pháp một điều kiện vòng lặp tùy ý và tìm vị trí nó được đặt và không khó hơn để chứng minh tính đúng đắn. Đối số về nơi đặt mã cũng khó hiểu khi có câu lệnh continue và không tốt hơn nhiều với câu lệnh if. Lập luận về hiệu quả là ngớ ngẩn.
David Thornley 14/09/09

23
Bất cứ khi nào bạn nhìn thấy "while (true)", bạn có thể nghĩ đến một vòng lặp có các điều kiện kết thúc bên trong, không khó hơn các điều kiện kết thúc tùy ý. Đơn giản, vòng lặp "while (foo)", trong đó foo là một biến boolean được đặt theo cách tùy ý, không tốt hơn "while (true)" với các dấu ngắt. Bạn phải xem qua mã trong cả hai trường hợp. Nhân tiện đưa ra một lý do ngớ ngẩn (và hiệu quả vi mô là một lý lẽ ngớ ngẩn) không giúp ích gì cho trường hợp của bạn.
David Thornley 14/09/09

5
@Dave: Tôi đã đưa ra lý do: Đó là cách duy nhất để có được một vòng lặp kiểm tra điều kiện ở giữa. Ví dụ cổ điển: while(true) {string line; std::getline(is,line); if(!is) break; lines.push_back(line);}Tất nhiên tôi có thể chuyển đổi điều này thành một vòng lặp được điều chỉnh trước (sử dụng std::getlinelàm điều kiện vòng lặp), nhưng điều này có nhược điểm của riêng nó ( linesẽ phải là một biến bên ngoài phạm vi của vòng lặp). Và nếu tôi làm vậy, tôi sẽ phải hỏi: Tại sao chúng ta lại có ba vòng? Chúng tôi luôn có thể biến mọi thứ thành một vòng lặp được điều chỉnh trước. Sử dụng những gì phù hợp nhất. Nếu while(true)phù hợp, sau đó sử dụng nó.
sbi 15/09/09

3
Theo phép lịch sự đối với những người đọc câu trả lời này, tôi sẽ tránh nhận xét về chất lượng của hàng hóa tạo ra câu hỏi và tập trung vào việc trả lời chính câu hỏi. Câu hỏi là một biến thể của câu sau, mà tôi tin rằng đây là một đặc điểm của nhiều ngôn ngữ: bạn có một vòng lặp được lồng vào bên trong một vòng lặp khác. Bạn muốn thoát ra khỏi vòng lặp bên ngoài khỏi vòng lặp bên trong. Làm thế nào là điều này được thực hiện?
Alex Leibowitz

172

Bạn có thể sử dụng goto.

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

   }
}
exit_loop: ;

12
Chỉ cần đừng hoang mang với từ khóa đó.
Fragsworth 14/09/09

45
Thật buồn cười khi tôi bị phản đối vì mọi người không thích goto. OP đã đề cập rõ ràng mà không sử dụng cờ . Bạn có thể đề xuất một cách tốt hơn, xem xét hạn chế của OP? :)
Mehrdad Afshari 14/09/09

18
ủng hộ chỉ để bù đắp cho những kẻ ghét goto vô tâm. Tôi đoán Mehrdad biết rằng anh ấy sẽ mất một vài điểm khi đề xuất giải pháp hợp lý ở đây.
Michael Krelin - hacker 14/09/09

6
+1, không có cách nào tốt hơn imho, ngay cả khi xem xét các hạn chế của OP. Cờ rất xấu, phá vỡ logic trên toàn bộ vòng lặp và do đó khó nắm bắt hơn.
Johannes Schaub - litb 14/09/09

11
vào cuối ngày, nếu không có cấu trúc goto, tất cả các ngôn ngữ đều thất bại. các ngôn ngữ cấp cao làm rối tung nó, nhưng nếu bạn quan sát đủ kỹ, bạn sẽ thấy mọi vòng lặp, hoặc điều kiện tổng hợp thành các nhánh có điều kiện và không điều kiện. chúng ta tự đánh lừa mình bằng cách sử dụng các từ khóa for, while, Until, switch, if, nhưng cuối cùng chúng đều sử dụng GOTO (hoặc JMP) theo cách này hay cách khác. bạn có thể tự huyễn hoặc mình, nghĩ ra đủ loại cách thông minh để che giấu nó, hoặc bạn có thể thành thật một cách thực dụng và sử dụng goto, nếu thích hợp và một cách tiết kiệm.
không đồng bộ hóa

58

Một giải pháp thay thế là sử dụng từ khóa continuekết hợp với break, tức là:

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

Sử dụng continuecâu lệnh để kết thúc mỗi nhãn trường hợp mà bạn muốn vòng lặp tiếp tục và sử dụng breakcâu lệnh để kết thúc các nhãn trường hợp kết thúc vòng lặp.

Tất nhiên giải pháp này chỉ hoạt động nếu không có mã bổ sung nào để thực thi sau câu lệnh switch.


9
Mặc dù điều này thực sự rất thanh lịch, nhưng nó có nhược điểm là hầu hết các nhà phát triển trước tiên phải xem nó trong một phút để hiểu cách thức / liệu điều này có hoạt động hay không. :(
sbi 15/09/09

8
Câu cuối cùng là những gì làm cho điều này không quá tuyệt vời :(Nếu không là một triển khai rất sạch sẽ.
nawfal

Khi bạn nghĩ về nó, không có mã máy tính nào là tốt. Chúng tôi chỉ được đào tạo để hiểu nó. Ví dụ xml trông giống như rác cho đến khi tôi học được nó. Bây giờ nó là ít rác hơn tìm kiếm. for (int x = 0; x <10; ++ x, moveCursor (x, y)) thực sự gây ra lỗi logic trong chương trình của tôi. Tôi quên rằng điều kiện cập nhật đã xảy ra ở cuối.

22

Một cách tốt nhất để làm điều này là đặt nó vào một hàm:

int yourfunc() {

    while(true) {

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

    }
}

Tùy chọn (nhưng 'phương pháp không tốt'): như đã đề xuất, bạn có thể sử dụng goto hoặc ném một ngoại lệ bên trong công tắc.


4
Ngoại lệ nên được sử dụng để, tốt, ném một ngoại lệ, không phải là một hành vi biết rõ của một hàm.
Clement Herreman, 14/09/09

Tôi đồng ý với Afterlife: Đặt nó vào một chức năng.
dalle 14/09/09

14
Ném một ngoại lệ thực sự là xấu trong trường hợp này. Nó có nghĩa là "Tôi muốn sử dụng goto, nhưng tôi đã đọc ở đâu đó rằng tôi không nên sử dụng chúng, vì vậy tôi sẽ đi với một trò chơi ngầm và giả vờ trông thông minh".
Gorpik 14/09/09

Tôi đồng ý rằng việc ném một ngoại lệ hoặc sử dụng goto là một ý tưởng tồi tệ, nhưng chúng là những lựa chọn hiệu quả và đã được liệt kê như vậy trong câu trả lời của tôi.
Alterlife 14/09/09

2
Ném một ngoại lệ là thay thế một COMEFROM cho một GOTO. Đừng làm vậy. Goto hoạt động tốt hơn nhiều.
David Thornley 14/09/09

14

AFAIK không có "ngắt đôi" hoặc cấu trúc tương tự trong C ++. Cái gần nhất sẽ là a goto- mặc dù nó có hàm ý xấu đối với tên của nó, nhưng tồn tại trong ngôn ngữ là có lý do - miễn là nó được sử dụng cẩn thận và tiết kiệm, đó là một lựa chọn khả thi.


8

Bạn có thể đặt công tắc của mình vào một chức năng riêng biệt như sau:

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

7

Ngay cả khi bạn không thích goto, không sử dụng ngoại lệ để thoát khỏi vòng lặp. Mẫu sau đây cho thấy nó có thể xấu như thế nào:

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

Tôi sẽ sử dụng gotonhư trong câu trả lời này . Trong trường hợp gotonày sẽ làm cho mã rõ ràng hơn sau đó bất kỳ tùy chọn nào khác. Tôi hy vọng rằng đây câu hỏi sẽ rất hữu ích.

Nhưng tôi nghĩ rằng sử dụng gotolà lựa chọn duy nhất ở đây vì chuỗi while(true). Bạn nên xem xét việc cấu trúc lại vòng lặp của mình. Tôi cho rằng giải pháp sau:

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

Hoặc thậm chí như sau:

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

1
Yeah, nhưng tôi giống như trường hợp ngoại lệ thậm chí còn ít ;-)
quamrana

3
Việc sử dụng một ngoại lệ để mô phỏng một goto tất nhiên sẽ tệ hơn thực sự sử dụng một goto! Nó cũng có thể sẽ chậm hơn đáng kể.
John Carter

2
@Downvoter: Đó là vì tôi nói rằng bạn không nên sử dụng ngoại lệ, hay vì tôi đề nghị tái cấu trúc?
Kirill V. Lyadvinsky 14/09/09

1
Không phải là người bỏ phiếu - nhưng ... Câu trả lời của bạn không rõ ràng là bạn đang đề xuất hay lên án ý tưởng sử dụng một ngoại lệ để thoát khỏi vòng lặp. Có lẽ câu đầu tiên nên là: "Ngay cả khi bạn không thích goto, không sử dụng một ngoại lệ để thoát khỏi một vòng lặp:"?
Jonathan Leffler 14/09/09

1
@Jonathan Leffler, Cảm ơn, bạn đã cho xem mẫu nhận xét có giá trị. đã cập nhật câu trả lời, ghi nhớ nhận xét của bạn.
Kirill V. Lyadvinsky 14/09/09

5

Không có cấu trúc C ++ nào để thoát ra khỏi vòng lặp trong trường hợp này.

Sử dụng cờ để ngắt vòng lặp hoặc (nếu thích hợp) trích xuất mã của bạn vào một hàm và sử dụng return.


2

Bạn có thể sử dụng goto, nhưng tôi muốn đặt cờ dừng vòng lặp. Sau đó thoát ra khỏi công tắc.


2

Tại sao không chỉ sửa điều kiện trong vòng lặp while của bạn, làm cho sự cố biến mất?

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

2

Không, C ++ không có cấu trúc cho điều này, vì từ khóa "break" đã được dành riêng để thoát khỏi khối chuyển đổi. Ngoài ra, một do.. while () với cờ thoát có thể là đủ.

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

1

Tôi nghĩ;

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

0

Cách đơn giản nhất để làm điều đó là đặt một IF đơn giản trước khi bạn thực hiện CHUYỂN ĐỔI và IF kiểm tra điều kiện của bạn để thoát khỏi vòng lặp .......... càng đơn giản càng tốt


2
Ừm ... bạn có thể đơn giản hơn nữa bằng cách đặt điều kiện đó vào đối số while. Ý tôi là nó ở đó để làm gì! :)
Mark A. Donohoe

0

Các breaktừ khóa trong C ++ chỉ chấm dứt lặp kèm theo nhiều nhất lồng nhau hoặc switchtuyên bố. Do đó, bạn không thể thoát ra khỏi while (true)vòng lặp trực tiếp trong switchcâu lệnh; tuy nhiên, bạn có thể sử dụng mã sau, mà tôi nghĩ là một mẫu tuyệt vời cho loại vấn đề này:

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

    //...
    }
}

Nếu bạn cần làm điều gì đó khi msg->statebằng DONE(chẳng hạn như chạy một quy trình dọn dẹp), thì hãy đặt mã đó ngay sau forvòng lặp; tức là nếu bạn hiện có:

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

    //...

    case DONE:
        do_cleanup();
        break;
    }

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

    msg = next_message();
}

Sau đó sử dụng thay thế:

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

    //...
    }
}

assert(msg->state == DONE);
do_cleanup();

0

Tôi ngạc nhiên là điều này đơn giản như thế nào khi xem xét chiều sâu của các lời giải thích ... Đây là tất cả những gì bạn cần ...

bool imLoopin = true;

while(imLoopin) {

    switch(msg->state) {

        case MSGTYPE: // ... 
            break;

        // ... more stuff ...

        case DONE:
            imLoopin = false;
            break;

    }

}

CƯỜI LỚN!! Có thật không! Đó là tất cả những gì bạn cần! Thêm một biến!


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

0

Tôi gặp vấn đề tương tự và đã giải quyết bằng cờ.

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

-2

Nếu tôi nhớ tốt cú pháp C ++, bạn có thể thêm nhãn vào các breakcâu lệnh, giống như for goto. Vì vậy, những gì bạn muốn sẽ được viết dễ dàng:

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

1
Thật không may, bộ nhớ của bạn bị lỗi. Đó sẽ là một tính năng hay, trong những dịp hiếm hoi mà nó sẽ hữu ích. (Tôi đã nhìn thấy các đề xuất về số lượng để phá vỡ từ, nhưng những cái nhìn với tôi như những con rệp chờ để xảy ra.)
David Thornley

Vậy thì đó phải là Java. Cảm ơn vì đã trả lời. Và, tất nhiên, bạn cũng đúng với phần còn lại.
Andrei Vajna II 14/09/09

-3
  while(true)
  {
    switch(x)
    {
     case 1:
     {
      break;
     }
    break;
   case 2:
    //some code here
   break;
  default:
  //some code here
  }
}

1
Tất cả các ngắt trong mẫu mã đó thoát ra khỏi câu lệnh switch.
Dan Hulme
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.