ฉันสามารถใช้ตัวแบ่งเพื่อออกจากลูปหลาย ๆ อัน 'สำหรับ' ลูปได้หรือไม่


305

เป็นไปได้ไหมที่จะใช้breakฟังก์ชั่นนี้เพื่อออกจากforลูปซ้อนหลายอัน?

ถ้าเป็นเช่นนั้นคุณจะทำสิ่งนี้อย่างไร คุณสามารถควบคุมจำนวนการbreakออกจากลูปได้หรือไม่


1
แทนที่จะใช้ break หรือ goto เพื่อออกจากลูปซ้อนกันหลายลูปคุณสามารถใส่ตรรกะนั้นในฟังก์ชันและใช้ return to exit จากลูปซ้อนหลายอัน วิธีนี้จะรักษาความสวยงามของรหัสของคุณและจะป้องกันไม่ให้คุณใช้ goto ซึ่งเป็นวิธีการเขียนโปรแกรมที่ไม่ดี
Rishab Shinghal

คำตอบ:


239

AFAIK, C ++ ไม่รองรับการตั้งชื่อลูปเช่น Java และภาษาอื่น ๆ คุณสามารถใช้ goto หรือสร้างค่าสถานะที่คุณใช้ ในตอนท้ายของแต่ละวงให้ตรวจสอบค่าสถานะ หากตั้งค่าเป็นจริงคุณสามารถแยกออกจากการทำซ้ำนั้น


317
อย่ากลัวที่จะใช้ a gotoหากเป็นตัวเลือกที่ดีที่สุด
jkeys

18
ฉันเป็นโปรแกรมเมอร์ C ++ คนใหม่ (และอีกคนไม่มีการฝึกอบรมการเขียนโปรแกรมอย่างเป็นทางการ) ดังนั้นหลังจากอ่านเกี่ยวกับการคุยโวของผู้คนใน goto ฉันลังเลที่จะใช้ด้วยความกลัวว่าโปรแกรมของฉันอาจระเบิดและฆ่าฉันในทันที นอกเหนือจากนั้นเมื่อฉันเคยเขียนโปรแกรมใน ti-83 ของฉัน (ในวิชาคณิตศาสตร์ที่น่าเบื่อ) ฟังก์ชั่นการแก้ไขพื้นฐานที่จำเป็นต้องใช้ของ goto
ทำ

26
@Faken: โปรแกรมเมอร์สองประเภทใช้goto: โปรแกรมเมอร์ที่ไม่ดีและโปรแกรมเมอร์ในทางปฏิบัติ อดีตเป็นคำอธิบายตนเอง หลังซึ่งคุณจะพอดีถ้าคุณเลือกที่จะใช้พวกเขาอย่างดีใช้แนวคิดที่เรียกว่า "ความชั่วร้าย" เมื่อมันเป็นความชั่วร้ายที่น้อยกว่า (สอง) อ่านสิ่งนี้เพื่อทำความเข้าใจแนวคิด C ++ บางอย่างที่คุณอาจจำเป็นต้องใช้เป็นครั้งคราว (แมโคร, goto's, preprocessor, อาร์เรย์): parashift.com/c++-faq-lite/big-picture.html#faq-6.15
jkeys

41
@Faken: ไม่มีอะไรผิดปกติกับการใช้ goto การใช้ข้ามไปผิดนั้นเป็นสิ่งที่ลำบาก
ทุกคน

28
@Hooked: ถูกต้องยกเว้นว่าการใช้gotoแทบจะไม่เคยเป็นตัวเลือกที่ดีที่สุด ทำไมไม่ใส่ลูปเข้าไปในฟังก์ชั่นของตัวเอง ( inlineถ้าคุณกังวลเรื่องความเร็ว) และreturnจากนี้
sbi

265

breakไม่ไม่ต้องเสียมันด้วย gotoนี่คือที่มั่นสุดท้ายที่เหลืออยู่สำหรับการใช้งานของ


19
มาตรฐานการเข้ารหัส MISRA C ++ อนุญาตให้ใช้ goto เพื่อครอบคลุมสถานการณ์ที่แน่นอนนี้
Richard Corden

11
โปรแกรมเมอร์จริงไม่กลัวที่จะใช้ข้ามไป ทำสำเร็จเป็นเวลา 25 ปีไม่มีความเสียใจช่วยประหยัดเวลาในการพัฒนาได้มาก
Doug Null

1
ฉันเห็นด้วย. gotos เป็นสิ่งจำเป็นหากคุณไม่มีความละเอียด 8K พิกเซลและจำเป็นต้องโทรตามลำดับการโทร Windows 30 ครั้ง
Michaël Roy

หรือ "คืนค่า" แบบง่ายโดยการใส่รหัสในฟังก์ชั่น
ธารา

68

เพียงเพิ่มคำตอบที่ชัดเจนโดยใช้ 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
        }
      }
    }();
  }

แน่นอนรูปแบบนี้มีข้อ จำกัด บางอย่างและเห็นได้ชัดว่า C ++ 11 เท่านั้น แต่ฉันคิดว่ามันค่อนข้างมีประโยชน์


7
นี่อาจทำให้ผู้อ่านสับสนในการคิดว่าการกลับมาเป็นสาเหตุให้ฟังก์ชั่นแลมบ์ดาอยู่ในการส่งคืนไม่ใช่แลมบ์ดาเอง
Xunie

3
@Xunie: ถ้าวงของคุณซับซ้อนมากจนคุณลืมไปว่าคุณอยู่ในแลมบ์ดามันถึงเวลาที่จะต้องใส่มันไว้ในฟังก์ชั่นที่แตกต่างออกไป แต่สำหรับกรณีง่าย ๆ มันน่าจะใช้ได้ดี
MikeMB

17
ฉันคิดว่าวิธีนี้สวยงาม
tuket

คุณสามารถจับแลมบ์ดาในเทมเพลต RIIA ที่ให้ชื่อและเรียกมันใน dtor (อาคาขอบเขต gaurd) สิ่งนี้จะไม่เพิ่มประสิทธิภาพการทำงานใด ๆ แต่สามารถปรับปรุงความสามารถในการอ่านได้และลดความเสี่ยงในการขาดวงเล็บการเรียกใช้ฟังก์ชัน
Red.Wave

56

อีกวิธีหนึ่งในการแยกออกจากลูปซ้อนกันคือการแยกลูปทั้งสองออกเป็นฟังก์ชันแยกต่างหากและreturnจากฟังก์ชันนั้นเมื่อคุณต้องการออก

แน่นอนว่าสิ่งนี้จะทำให้เกิดข้อโต้แย้งอื่น ๆ ว่าคุณควรชัดเจนreturnจากฟังก์ชั่นที่ใดก็ตามที่นอกเหนือจากในตอนท้าย


7
นั่นคือปัญหา C ด้วยการส่งคืนต้น RIAA ไม่ใช่ปัญหาเนื่องจากปัญหาทั้งหมดที่เกี่ยวข้องกับการส่งคืนต้นได้รับการจัดการอย่างถูกต้อง
มาร์ตินนิวยอร์ก

4
ฉันเข้าใจว่าแอปพลิเคชันที่เหมาะสมของ RIAA สามารถแก้ปัญหาการล้างทรัพยากรใน C ++ ได้ แต่ฉันได้เห็นข้อโต้แย้งทางปรัชญาต่อการกลับมาเริ่มต้นดำเนินการต่อในสภาพแวดล้อมและภาษาอื่น ๆ ระบบหนึ่งที่ฉันทำงานที่ซึ่งมาตรฐานการเข้ารหัสที่ห้ามส่งคืนก่อนกำหนดมีฟังก์ชันที่ทิ้งกระจุยกระจายกับตัวแปรบูลีน (ที่มีชื่ออย่างcontinue_processing) ที่ควบคุมการทำงานของบล็อกของโค้ดต่อไปในฟังก์ชัน
เกร็ก Hewgill

21
RIAA คืออะไร นั่นคืออะไรเช่น RAII? = D
jkeys

1
ขึ้นอยู่กับว่าเขามีลูปกี่ตัวและรังนั้นลึกแค่ไหน ... คุณต้องการเม็ดสีน้ำเงินหรือเม็ดแดงหรือไม่?
แมตต์

34

หยุดพักจะออกจากลูปที่อยู่ด้านในสุดเท่านั้น

คุณสามารถใช้gotoเพื่อแยกลูปออกเป็นจำนวนเท่าใดก็ได้

แน่นอนกลับไปข้างมักจะพิจารณาอันตราย

เหมาะสมที่จะใช้ฟังก์ชั่นหยุดพัก [... ]?

การใช้ตัวแบ่งและการข้ามไปอาจทำให้เหตุผลที่ยากขึ้นเกี่ยวกับความถูกต้องของโปรแกรม ดูที่นี่สำหรับการอภิปรายเกี่ยวกับเรื่องนี้: Dijkstra ไม่ได้บ้า


16
คำตอบที่ดีในการอธิบายว่า "goto เป็นอันตราย" meme เชื่อมโยงอย่างยิ่งกับคำสั่ง "การควบคุมการขัดจังหวะการไหลที่ไม่เป็นอันตราย" โดยทั่วไป มันเป็นความหมายที่จะบอกว่า "โกโตะเป็นอันตราย" แล้วหันไปรอบ ๆ และแนะนำให้ใช้หรือbreak return
Pavel Minaev

6
@Pavel: breakและreturnมีความได้เปรียบมากกว่าgotoที่คุณไม่จำเป็นต้องตามหาฉลากเพื่อค้นหาว่าพวกเขาไปที่ไหน ใช่ภายใต้พวกเขาเป็นบางประเภทgotoแต่มีข้อ จำกัด มาก gotoพวกเขาจะง่ายมากที่จะถอดรหัสโดยโปรแกรมเมอร์ของสมองรูปแบบจับคู่กว่าไม่ จำกัด ดังนั้น IMO จึงเหมาะสมกว่า
sbi

@sbi: จริง แต่ตัวแบ่งยังไม่ได้เป็นส่วนหนึ่งของการเขียนโปรแกรมที่มีโครงสร้าง gotoมันเป็นเพียงการดีกว่าทนกว่า
jkeys

2
@KarlVoigtland ลิงก์ Dijkstra ล้าสมัยแล้ว สิ่งนี้ดูเหมือนจะใช้งานได้: blog.plover.com/2009/07
Aaron Brager

3
ไม่มีอะไรผิดปกติอย่างแน่นอนเมื่อใช้ goto ในสถานการณ์นี้ goto ที่วางไว้อย่างดีคือ leaps และขอบเขตที่ดีกว่าและอ่านได้ง่ายกว่าโซลูชันที่บิดเบี้ยวหลายตัวที่เสนอเป็นอย่างอื่น
James

22

แม้ว่าคำตอบนี้ถูกนำเสนอไปแล้ว แต่ฉันคิดว่าวิธีการที่ดีคือทำดังต่อไปนี้:

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
ซึ่งเป็นสิ่งที่ดี แต่ก็ยังอ่านไม่ได้ฉันต้องการ goto ในกรณีนั้น
ПетърПетров

2
ทำให้โค้ดของคุณค่อนข้าง "ช้า" เนื่องจากgotoMainLoopมีการตรวจสอบทุกรอบ
โทมัส

1
ในกรณีนี้การใช้งานจริงgotoทำให้คอร์สามารถอ่านได้และมีประสิทธิภาพมากขึ้น
ПетърПетров

20

แล้วเรื่องนี้ล่ะ

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
วิธีการที่น่าสนใจ แต่ฉันชอบวิธีที่ ered @ inf.ig.sh สำหรับ (int ที่ไม่ได้ลงชื่อ y = 0; y <y_max &&! gotoMainLoop; y ++)
Andy

15

ตัวอย่างโค้ดที่ใช้gotoและเลเบลเพื่อแยกลูปซ้อนกัน:

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

11

วิธีที่ดีวิธีหนึ่งในการแบ่งลูปซ้อนหลาย ๆ อันให้ปรับโครงสร้างโค้ดของคุณเป็นฟังก์ชัน:

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
... ซึ่งไม่ใช่ตัวเลือกถ้าเราต้องผ่าน 10-20 ตัวแปรสำหรับ stack framing function นี้
ПетърПетров

1
@ ПетърПетровจากนั้นไปที่แลมบ์ดาซึ่งดีกว่าเพราะคุณสามารถกำหนดได้อย่างที่คุณต้องการ
DarioP

+1 สำหรับ lambdas แต่ยกเครื่องใน core engine ของเกมที่แม้แต่ stack frame เดียวก็ยังเป็นคอขวด ขออภัยที่จะบอก แต่ lambdas ไม่เบาอย่างน้อยใน MSVC 2010
ПетърПетров

@ ПетърПетровจากนั้นเปลี่ยนฟังก์ชั่นคู่ให้เป็นคลาสและตัวแปรสแต็กเป็นสมาชิกส่วนตัว
Arthur Tacca

นี่เป็นการซับซ้อนรหัสที่ซับซ้อนแล้วเท่านั้น :) บางครั้ง goto เป็นทางออกเดียว หรือถ้าคุณเขียน automata ที่ซับซ้อนด้วยเอกสาร "goto state X" ดังนั้น goto ก็คือการทำให้โค้ดอ่านตามที่เขียนในเอกสาร นอกจากนี้ C # และไปทั้งสองมี goto โดยมีวัตถุประสงค์: ไม่มีภาษาทัวริงสมบูรณ์โดยไม่มี goto และ gotos มักจะเป็นเครื่องมือที่ใช้ดีที่สุดสำหรับการเขียนแปลกลางหรือรหัสเหมือนการชุมนุม
ПетърПетров

5

ข้ามไปจะมีประโยชน์มากสำหรับการแบ่งลูปซ้อนกัน

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

breakคำสั่งยุติการดำเนินการของการปิดล้อมที่ใกล้ที่สุดdo, for, switchหรือwhileคำสั่งที่ปรากฏ การควบคุมผ่านไปยังคำสั่งที่ตามหลังคำสั่งที่ถูกยกเลิก

จากMSDN


3

ฉันคิดว่า gotoใช้ได้ในกรณีนี้:

เพื่อจำลอง a break/continueคุณต้องการ:

หยุดพัก

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

ต่อ

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

"ดำเนินการต่อ" จะไม่ทำงานที่นั่นเพราะนิพจน์วนซ้ำของลูปแรกจะไม่ถูกเรียกใช้
Fabio A.

ฉันสมมติว่า iterator iสำหรับวงแรกคือ ดังนั้นi++ก่อนที่จะข้ามไป
JuliusAlphonso

0

ภาษาอื่น ๆ เช่น PHP ยอมรับพารามิเตอร์สำหรับตัวแบ่ง (เช่น break 2;) เพื่อระบุจำนวนของระดับลูปที่ซ้อนกันที่คุณต้องการแยกออก แต่ C ++ ไม่ได้ คุณจะต้องทำมันออกมาโดยใช้บูลีนที่คุณตั้งค่าเป็นเท็จก่อนที่จะวนซ้ำตั้งค่าเป็นจริงในลูปถ้าคุณต้องการที่จะทำลายรวมทั้งตัวแบ่งตามเงื่อนไขหลังจากวนซ้อนกันตรวจสอบว่าบูลีนถูกตั้งค่าเป็นจริง และทำลายถ้าใช่


0

ฉันรู้ว่านี่คือโพสต์เก่า แต่ฉันขอแนะนำคำตอบที่สมเหตุสมผลและเรียบง่าย

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
นั่นไม่ใช่วิธีการแก้ปัญหาที่ปรับขนาดได้มากที่สุดเท่าที่จะไม่ทำงานถ้าคุณมีกริยาที่ซับซ้อนแทนj = conditionj j < conditionj
Sergey

0

ทำลายลูปจำนวนเท่าใดก็ได้ด้วยboolตัวแปรเพียงตัวเดียวดูด้านล่าง:

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

ในรหัสนี้เราbreak;ทุกคนลูป


0

ฉันไม่แน่ใจว่ามันคุ้มค่าหรือไม่ แต่คุณสามารถเลียนแบบลูปของ Java ที่มีมาโครง่ายๆ:

#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

ตัวอย่างการใช้งาน:

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

ตัวอย่างอื่น:

#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

คุณสามารถใช้ลอง ... จับ

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

หากคุณต้องแบ่งออกเป็นหลายลูปในครั้งเดียวก็มักจะเป็นข้อยกเว้นต่อไป


1
ฉันต้องการทราบสาเหตุที่คำตอบนี้ได้รับคะแนนเสียงลงมาก
hkBattousai

6
@hkBattousai วิธีการแก้ปัญหามีคะแนนลงเพราะใช้ข้อยกเว้นในการควบคุมการไหลของการดำเนินการ ตามชื่อที่แนะนำควรใช้ข้อยกเว้นในกรณีพิเศษเท่านั้น
Helio Santos

4
@HelioSantos นี่ไม่ใช่สถานการณ์ที่พิเศษสำหรับภาษาที่ไม่ได้แก้ปัญหาที่เหมาะสมหรือไม่
hkBattousai

8
ข้อยกเว้นช้า
กอร์ดอน

2
ผลกระทบด้านประสิทธิภาพของการโยนมีขนาดใหญ่มากสำหรับสิ่งที่ไม่ใช่ข้อผิดพลาดที่ไม่สามารถกู้คืนได้ 99% ของเวลา
Michaël Roy

-4

การแตกออกจาก for-loop นั้นค่อนข้างแปลกสำหรับฉันเนื่องจากความหมายของ for-loop นั้นโดยทั่วไปจะระบุว่ามันจะทำงานตามจำนวนที่ระบุ อย่างไรก็ตามมันก็ไม่ได้เลวร้ายในทุกกรณี หากคุณกำลังค้นหาบางอย่างในคอลเลกชันและต้องการทำลายหลังจากที่คุณพบมันมีประโยชน์ การแยกออกจากลูปซ้อนกันเป็นไปไม่ได้ใน C ++ มันเป็นภาษาอื่น ๆ ผ่านการใช้ตัวแบ่งที่มีป้ายกำกับ คุณสามารถใช้ป้ายกำกับและข้ามไปได้ แต่นั่นอาจทำให้คุณอิจฉาริษยาตอนกลางคืน .. ? ดูเหมือนว่าตัวเลือกที่ดีที่สุดแม้ว่า


11
มันไม่แปลกเลย หากคุณกำลังวนซ้ำคอลเลกชันเพื่อค้นหาบางสิ่งบางอย่าง (และไม่มีวิธีการค้นหาที่เร็วขึ้น) ก็ไม่มีจุดสิ้นสุดของลูป (เป็นตัวอย่างหนึ่ง)
โจ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.