Tôi có thể sử dụng break để thoát nhiều vòng lặp 'for' lồng nhau không?


304

Có thể sử dụng breakchức năng để thoát khỏi một số forvòng lặp lồng nhau ?

Nếu vậy, làm thế nào bạn sẽ làm điều này? Bạn cũng có thể kiểm soát bao nhiêu vòng breakthoát?


1
Thay vì sử dụng break hoặc goto để thoát khỏi nhiều vòng lặp lồng nhau, bạn có thể đưa logic đó vào một hàm và sử dụng return để thoát khỏi nhiều vòng lặp lồng nhau. Điều này sẽ duy trì tính thẩm mỹ của mã của bạn và sẽ ngăn bạn sử dụng goto, đây là một cách lập trình xấu.
Rishab Shinghal

Câu trả lời:


239

AFAIK, C ++ không hỗ trợ các vòng lặp đặt tên, giống như Java và các ngôn ngữ khác. Bạn có thể sử dụng một goto hoặc tạo một giá trị cờ mà bạn sử dụng. Vào cuối mỗi vòng lặp kiểm tra giá trị cờ. Nếu nó được đặt thành true, thì bạn có thể thoát ra khỏi lần lặp đó.


317
Đừng ngại sử dụng gotonếu đó là lựa chọn tốt nhất.
lừa

18
Tôi là một lập trình viên C ++ mới (và một người không được đào tạo lập trình chính thức), do đó, sau khi đọc về những lời tán dương của mọi người trên goto. Tôi do dự khi sử dụng nó vì sợ chương trình của tôi có thể đột nhiên phát nổ và giết chết tôi. Ngoài ra, khi tôi thường viết chương trình trên ti-83 (dĩ nhiên là trong lớp toán nhàm chán), các chức năng mà trình soạn thảo cơ bản cung cấp yêu cầu sử dụng goto's.
Được thực hiện vào

26
@Faken: Hai loại lập trình viên sử dụng goto: Lập trình viên tồi và lập trình viên thực dụng. Các cựu là tự giải thích. Loại thứ hai, mà bạn sẽ phù hợp nếu bạn chọn sử dụng chúng tốt, sử dụng khái niệm được gọi là "xấu xa" khi nó là ít hơn (hai) tệ nạn. Đọc phần này để hiểu rõ hơn về một số khái niệm C ++ mà thỉnh thoảng
lừa

41
@Faken: Không có gì sai khi sử dụng goto. Đó là lạm dụng một goto là rắc rối.
Mọi người

28
@Hooked: Điều đó đúng, ngoại trừ việc sử dụng gotohiếm khi là lựa chọn tốt nhất. Tại sao không đặt các vòng lặp vào chức năng của riêng họ ( inline, nếu bạn quan tâm đến tốc độ) và returntừ điều này?
sbi

265

Không, đừng làm hỏng nó với a break. Đây là thành trì cuối cùng còn lại để sử dụng goto.


19
Tiêu chuẩn mã hóa MISRA C ++ cho phép sử dụng goto để bao quát loại tình huống chính xác này.
Richard Corden

11
Các lập trình viên thực sự không ngại sử dụng goto. Thực hiện nó trong 25 năm - không hối tiếc - tiết kiệm rất nhiều thời gian phát triển.
Doug Null

1
Tôi đồng ý. gotos là điều bắt buộc nếu bạn không có 8K pixel và cần gọi một chuỗi 30 cuộc gọi HĐH Windows.
Michaël Roy

Hoặc một "trở lại" đơn giản bằng cách đặt mã trong một hàm.
Tara

68

Chỉ cần thêm một câu trả lời rõ ràng bằng lambdas:

  for (int i = 0; i < n1; ++i) {
    [&] {
      for (int j = 0; j < n2; ++j) {
        for (int k = 0; k < n3; ++k) {
          return; // yay we're breaking out of 2 loops here
        }
      }
    }();
  }

Tất nhiên mẫu này có một số hạn chế nhất định và rõ ràng chỉ có C ++ 11 nhưng tôi nghĩ nó khá hữu ích.


7
Điều này có thể khiến người đọc nhầm lẫn khi nghĩ rằng sự trở lại làm cho chức năng của lambda trở lại, và không phải là chính lambda.
Xunie

3
@Xunie: Nếu vòng lặp của bạn quá phức tạp, đến nỗi bạn quên rằng bạn đang ở trong lambda, đã đến lúc đặt chúng vào một chức năng khác, nhưng đối với các trường hợp đơn giản, điều này sẽ hoạt động khá tốt.
MikeMB

17
Tôi nghĩ giải pháp này rất hay
tuket

Bạn có thể chụp lambda trong một mẫu RIIA đặt tên cho nó và gọi nó trong dtor (hay còn gọi là scope gaurd). Điều này sẽ không thêm bất kỳ mức tăng hiệu suất nào nhưng có thể cải thiện khả năng đọc và giảm nguy cơ thiếu dấu ngoặc đơn gọi hàm.
Màu đỏ. Có

56

Một cách tiếp cận khác để thoát ra khỏi một vòng lặp lồng nhau là đưa cả hai vòng lặp vào một chức năng riêng biệt và return từ chức năng đó khi bạn muốn thoát.

Tất nhiên, điều này đưa ra lập luận khác về việc bạn có nên dứt khoát returntừ một chức năng ở bất kỳ nơi nào khác ngoài cuối không.


7
Đó là một vấn đề C. Với RIAA, việc hoàn trả sớm không phải là vấn đề vì tất cả các vấn đề liên quan đến việc hoàn trả sớm đều được xử lý chính xác.
Martin York

4
Tôi hiểu rằng việc áp dụng đúng RIAA có thể giải quyết vấn đề dọn sạch tài nguyên trong C ++, nhưng tôi đã thấy lập luận triết học chống lại sự trở lại sớm tiếp tục trong các môi trường và ngôn ngữ khác. Một hệ thống mà tôi đã làm việc trong đó tiêu chuẩn mã hóa bị cấm hoàn trả sớm có các hàm chứa đầy các biến boolean (có tên như continue_processing) điều khiển việc thực thi các khối mã nằm sâu hơn trong hàm.
Greg Hewgill

21
RIAA là gì? Đó có phải là bất cứ điều gì như RAII? = D
jkeys

1
Phụ thuộc vào số lượng vòng lặp mà anh ta có và độ sâu của tổ ... bạn muốn viên thuốc màu xanh hay viên thuốc màu đỏ?
Matt

34

phá vỡ sẽ chỉ thoát khỏi vòng lặp trong cùng có chứa nó.

Bạn có thể sử dụng goto để thoát ra khỏi bất kỳ số vòng lặp.

Tất nhiên goto thường được coi là có hại .

Có đúng không khi sử dụng chức năng ngắt [...]?

Sử dụng break và goto có thể làm cho khó khăn hơn để suy luận về tính đúng đắn của một chương trình. Xem ở đây để thảo luận về điều này: Dijkstra không điên .


16
Một câu trả lời hay ở chỗ nó giải thích rằng meme "goto là có hại" được liên kết chặt chẽ với câu lệnh "gián đoạn dòng điều khiển" có hại "tổng quát hơn. Thật vô nghĩa khi nói "goto là có hại", sau đó quay lại và khuyên bạn nên sử dụng breakhoặc return.
Pavel Minaev

6
@Pavel: breakreturncó lợi thế hơn gotolà bạn không cần phải tìm nhãn để tìm nơi họ đến. Vâng, bên dưới họ là một số loại goto, nhưng một loại rất hạn chế. Chúng dễ dàng giải mã hơn rất nhiều bởi bộ não phù hợp với lập trình viên so với bộ não không bị hạn chế goto. Vì vậy, IMO họ thích hợp hơn.
sbi

@sbi: Đúng, nhưng break vẫn không phải là một phần của lập trình có cấu trúc. Nó chỉ là dung nạp tốt hơn a goto.
lừa

2
@KarlVoigtland liên kết Dijkstra đã lỗi thời; điều này dường như đang hoạt động: blog.plover.com/2009/07
Aaron Brager

3
Hoàn toàn không có gì sai khi sử dụng goto trong tình huống này. Một goto được đặt tốt là bước nhảy vọt và tốt hơn và dễ đọc hơn nhiều giải pháp bị bóp méo khác được đề xuất.
James

22

Mặc dù câu trả lời này đã được trình bày, tôi nghĩ rằng một cách tiếp cận tốt là làm như sau:

for(unsigned int z = 0; z < z_max; z++)
{
    bool gotoMainLoop = false;
    for(unsigned int y = 0; y < y_max && !gotoMainLoop; y++)
    {
        for(unsigned int x = 0; x < x_max && !gotoMainLoop; x++)
        {
                          //do your stuff
                          if(condition)
                            gotoMainLoop = true;
        }
    }

}

5
đó là tốt nhưng vẫn không vì thế có thể đọc được, tôi muốn goto trong trường hợp đó
Петър Петров

2
điều này làm cho mã của bạn "khá" chậm vì gotoMainLoopđược kiểm tra mỗi chu kỳ
Thomas

1
Trong trường hợp này, sử dụng thực tế gotolàm cho lõi dễ đọc hơn và hoạt động tốt hơn.
Петър Петров

20

Còn cái này thì sao?

for(unsigned int i=0; i < 50; i++)
{
    for(unsigned int j=0; j < 50; j++)
    {
        for(unsigned int k=0; k < 50; k++)
        {
            //Some statement
            if (condition)
            {
                j=50;
                k=50;
            }
        }
    }
}

2
Cách tiếp cận thú vị nhưng tôi chắc chắn thích cách ered @ inf.ig.sh xử lý nó. for (unsign int y = 0; y <y_max &&! gotoMainLoop; y ++).
Andy

15

Một ví dụ mã sử dụng gotovà nhãn để thoát ra khỏi vòng lặp lồng nhau:

for (;;)
  for (;;)
    goto theEnd;
theEnd:

11

Một cách hay để thoát ra khỏi một số vòng lặp lồng nhau là cấu trúc lại mã của bạn thành một hàm:

void foo()
{
    for(unsigned int i=0; i < 50; i++)
    {
        for(unsigned int j=0; j < 50; j++)
        {
            for(unsigned int k=0; k < 50; k++)
            {
                // If condition is true
                return;
            }
        }
    }
}

4
... Đó không phải là một tùy chọn nếu chúng ta phải vượt qua 10-20 biến để xếp khung cho hàm này.
Петър Петров

1
@ Trả lời sau đó đi lambda cũng tốt hơn vì bạn có thể xác định chính xác nơi bạn cần.
DarioP

+1 cho lambdas nhưng đại tu trong lõi công cụ trò chơi trong đó ngay cả một khung ngăn xếp vẫn là một nút cổ chai. Xin lỗi vì đã nói, nhưng lambdas không nên trọng lượng nhẹ ít nhất trong MSVC 2010.
Петър Петров

@ ПетърNETетров Sau đó thay đổi cặp hàm thành một lớp và các biến ngăn xếp thành các thành viên riêng.
Arthur Tacca

Điều này chỉ vượt qua mã đã phức tạp :) Đôi khi, goto là giải pháp duy nhất. Hoặc nếu bạn viết automata phức tạp với tài liệu "goto state X", thì goto thực sự đang làm cho mã được đọc như được viết trong tài liệu. Ngoài ra, C # và cả hai đều có goto với một mục đích: không có ngôn ngữ nào được hoàn thiện mà không có goto và gotos thường là những công cụ được sử dụng tốt nhất để viết trình dịch trung gian hoặc mã giống như lắp ráp.
Петър Петров

5

goto có thể rất hữu ích cho việc phá vỡ các vòng lặp lồng nhau

for (i = 0; i < 1000; i++) {
    for (j = 0; j < 1000; j++) {
        for (k = 0; k < 1000; k++) {
             for (l = 0; l < 1000; l++){
                ....
                if (condition)
                    goto break_me_here;
                ....
            }
        }
    }
}

break_me_here:
// Statements to be executed after code breaks at if condition

3

Các breaktuyên bố chấm dứt việc thực hiện kèm theo gần do, for, switch, hoặc whiletuyên bố trong đó nó xuất hiện. Kiểm soát chuyển đến câu lệnh theo sau câu lệnh kết thúc.

từ msd .


3

Tôi nghĩ rằng một gotolà hợp lệ trong trường hợp này:

Để mô phỏng a break/ continue, bạn muốn:

Phá vỡ

for ( ;  ;  ) {
    for ( ;  ;  ) {
        /*Code here*/
        if (condition) {
            goto theEnd;
        }
    }
}
theEnd:

Tiếp tục

for ( ;  ; ) {
    for ( ;  ;  ) {
        /*Code here*/
        if (condition) {
            i++;
            goto multiCont;
        }
    }
    multiCont:
}

"Tiếp tục" sẽ không hoạt động ở đó, vì biểu thức lặp của vòng lặp đầu tiên sẽ không được thực thi
Fabio A.

Tôi giả sử rằng iterator cho vòng lặp đầu tiên là i. Do đó i++trước goto
JuliusAlphonso

0

Các ngôn ngữ khác như PHP chấp nhận một tham số cho break (tức là break 2;) để chỉ định mức độ vòng lặp lồng nhau mà bạn muốn thoát ra, tuy nhiên C ++ thì không. Bạn sẽ phải xử lý nó bằng cách sử dụng boolean mà bạn đặt thành false trước vòng lặp, đặt thành true trong vòng lặp nếu bạn muốn ngắt, cộng với ngắt điều kiện sau vòng lặp lồng nhau, kiểm tra xem boolean có được đặt thành true không và phá vỡ nếu có.


0

Tôi biết đây là bài cũ. Nhưng tôi sẽ đề nghị một câu trả lời hợp lý và đơn giản hơn.

for(unsigned int i=0; i < 50; i++)
    {
        for(unsigned int j=0; j < conditionj; j++)
        {
            for(unsigned int k=0; k< conditionk ; k++)
            {
                // If condition is true

                j= conditionj;
               break;
            }
        }
    }

3
Đó không phải là giải pháp có khả năng mở rộng vì j = conditionjsẽ không hoạt động nếu bạn có một vị từ phức tạp thay vì j < conditionj.
Serge

0

Phá vỡ bất kỳ số vòng lặp chỉ bằng một boolbiến xem bên dưới:

bool check = true;

for (unsigned int i = 0; i < 50; i++)
{
    for (unsigned int j = 0; j < 50; j++)
    {
        for (unsigned int k = 0; k < 50; k++)
        {
            //Some statement
            if (condition)
            {
                check = false;
                break;
            }
        }
        if (!check)
        {
            break;
        }
    }
    if (!check)
    {
        break;
    }
}

Trong mã này, break;tất cả các vòng lặp.


0

Tôi không chắc nó có đáng hay không, nhưng bạn có thể mô phỏng các vòng lặp có tên của Java bằng một vài macro đơn giản:

#define LOOP_NAME(name) \
    if ([[maybe_unused]] constexpr bool _namedloop_InvalidBreakOrContinue = false) \
    { \
        [[maybe_unused]] CAT(_namedloop_break_,name): break; \
        [[maybe_unused]] CAT(_namedloop_continue_,name): continue; \
    } \
    else

#define BREAK(name) goto CAT(_namedloop_break_,name)
#define CONTINUE(name) goto CAT(_namedloop_continue_,name)

#define CAT(x,y) CAT_(x,y)
#define CAT_(x,y) x##y

Ví dụ sử dụng:

#include <iostream>

int main()
{
    // Prints:
    // 0 0
    // 0 1
    // 0 2
    // 1 0
    // 1 1

    for (int i = 0; i < 3; i++) LOOP_NAME(foo)
    {
        for (int j = 0; j < 3; j++)
        {
            std::cout << i << ' ' << j << '\n';
            if (i == 1 && j == 1)
                BREAK(foo);
        }
    }
}

Một vi dụ khac:

#include <iostream>

int main()
{
    // Prints: 
    // 0
    // 1
    // 0
    // 1
    // 0
    // 1

    int count = 3;
    do LOOP_NAME(foo)
    {
        for (int j = 0; j < 3; j++)
        {
            std::cout << ' ' << j << '\n';
            if (j == 1)
                CONTINUE(foo);
        }
    }
    while(count-- > 1);
}

-1
  while (i<n) {
    bool shouldBreakOuter = false;
    for (int j=i + 1; j<n; ++j) {
      if (someCondition) {
          shouldBreakOuter = true;
      }
    }

    if (shouldBreakOuter == true)
      break;

  }

-3

Bạn có thể sử dụng thử ... bắt.

try {
    for(int i=0; i<10; ++i) {
        for(int j=0; j<10; ++j) {
            if(i*j == 42)
                throw 0; // this is something like "break 2"
        }
    }
}
catch(int e) {} // just do nothing
// just continue with other code

Nếu bạn phải thoát ra khỏi một số vòng lặp cùng một lúc, thì đó thường là một ngoại lệ.


1
Tôi muốn biết lý do tại sao câu trả lời này nhận được nhiều phiếu bầu.
hkBattousai

6
@hkBattousai Giải pháp đã bỏ phiếu vì nó sử dụng ngoại lệ để kiểm soát luồng thực thi. Như tên cho thấy, ngoại lệ chỉ nên được sử dụng trong các trường hợp ngoại lệ.
Helio Santos

4
@HelioSantos Đây không phải là một tình huống đặc biệt mà ngôn ngữ không cung cấp giải pháp phù hợp?
hkBattousai

8
Ngoại lệ là chậm.
Gordon

2
Tác động hiệu suất của cú ném là rất lớn đối với một thứ không phải là lỗi không thể phục hồi 99% thời gian.
Michaël Roy

-4

Việc thoát ra khỏi vòng lặp for là một điều kỳ lạ đối với tôi, vì ngữ nghĩa của vòng lặp for thường chỉ ra rằng nó sẽ thực thi một số lần xác định. Tuy nhiên, nó không tệ trong mọi trường hợp; nếu bạn đang tìm kiếm thứ gì đó trong bộ sưu tập và muốn phá vỡ sau khi bạn tìm thấy nó, điều đó hữu ích. Tuy nhiên, việc thoát ra khỏi các vòng lặp lồng nhau là không thể có trong C ++; đó là trong các ngôn ngữ khác thông qua việc sử dụng một dấu ngắt. Bạn có thể sử dụng nhãn và goto, nhưng điều đó có thể khiến bạn ợ nóng vào ban đêm ..? Có vẻ như là lựa chọn tốt nhất mặc dù.


11
Không có gì lạ cả. Nếu bạn đang lặp đi lặp lại một bộ sưu tập để tìm kiếm thứ gì đó (và không có cách tìm kiếm nhanh hơn), sẽ không có điểm nào kết thúc vòng lặp. (như một ví dụ)
Joe
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.