Чи можу я використовувати перерву для виходу з декількох вкладених циклів "for"?


305

Чи можливо за допомогою breakфункції вийти з декількох вкладених forциклів?

Якщо так, то як би ви зробили це робити? Чи можете ви також контролювати кількість циклів breakвиходів?


1
Замість того, щоб використовувати break або goto для виходу з декількох вкладених циклів, ви можете вкласти цю конкретну логіку у функцію та використовувати return для виходу з декількох вкладених циклів. Це дозволить зберегти естетику вашого коду і не дасть вам використовувати goto, що є поганою практикою програмування.
Рішаб Шингхал

Відповіді:


239

AFAIK, C ++ не підтримує цикли іменування, як це роблять Java та інші мови. Ви можете використовувати goto або створити значення прапора, яке ви використовуєте. В кінці кожного циклу перевірте значення прапора. Якщо встановлено значення true, ви можете вирватися з цієї ітерації.


317
Не бійтеся використовувати, gotoякщо це найкращий варіант.
jkeys

18
Я новий програміст на C ++ (і той, хто не має жодної офіційної програми програмування), після того, як прочитав про рейтинги людей на goto. Я не вагаюся щодо його використання, боячись, що моя програма може раптом вибухнути і вбити мене. Крім цього, коли я писав програми на моєму ти-83 (звичайно, в нудному класі математики), функції, які надав основний редактор, вимагали використання goto.
Факен

26
@Faken: два типи програмістів використовують goto: погані програмісти та прагматичні програмісти. Перші пояснюють себе. Останні, до яких ви б вписалися, якщо вирішите добре їх використовувати, використовуйте так звану концепцію "зла", коли вона менша з (двох) злих. Прочитайте це, щоб краще зрозуміти деякі поняття C ++, які, можливо, вам доведеться час від часу використовувати (макроси, goto, препроцесор, масиви): parashift.com/c++-faq-lite/big-picture.html#faq-6.15
jkeys

41
@Faken: Немає нічого поганого у використанні goto. Це неправильне використання Goto, що викликає клопоти.
Усі

28
@Hooked: Це правильно, за винятком того, що використання gotoрідко коли-небудь є найкращим варіантом. Чому б не ввести петлі у власну функцію ( inlineякщо вас турбує швидкість) і returnз цього?
sbi

265

Ні, не псуйте це з break. Це останній оплот для використання goto.


19
Стандарт кодування MISRA C ++ дозволяє використовувати goto для висвітлення такої ситуації.
Річард Корден

11
Справжні програмісти не бояться використовувати goto. Робили це протягом 25 років - не шкодуйте - врятували тонну часу на розробку.
Doug Null

1
Я згоден. gotos є обов'язковим, якщо у вас немає 8К пікселів поперек, і вам потрібно викликати послідовність з 30 викликів ОС Windows.
Michaël Roy

Або просте "повернення", поставивши код у функцію.
Тара

68

Просто, щоб додати явну відповідь за допомогою лямбда:

  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
Це може заплутати читача в думці, що повернення викликає функцію повернення лямбда, а не саму лямбду.
Сюні

3
@Xunie: Якщо ваш цикл настільки складний, що ви забуваєте, що ви перебуваєте в лямбда, настав час перевести їх на іншу функцію, але для простих випадків це має працювати досить добре.
MikeMB

17
Я думаю, що це рішення прекрасне
tuket

Ви можете зафіксувати лямбда в шаблоні RIIA, який дає йому ім'я та викликає його в dtor (він же область гаурд). Це не призведе до збільшення продуктивності, але може покращити читабельність та знизити ризик відсутності дужок функції виклику функції.
Red.Wave

56

Інший підхід до виходу з вкладеного циклу полягає в розподілі обох циклів на окрему функцію, а також returnіз цієї функції, коли потрібно вийти.

Звичайно, це підводить інший аргумент того, чи слід коли-небудь явно returnвиконувати функції з будь-якої іншої точки, ніж в кінці.


7
Це проблема C. З RIAA раннє повернення не є проблемою, оскільки всі проблеми, пов’язані з раннім поверненням, правильно обробляються.
Мартін Йорк

4
Я розумію, що правильне застосування RIAA може вирішити проблему очищення ресурсів у C ++, але я бачив, як філософський аргумент проти раннього повернення продовжується в інших середовищах та мовах. Одна система, над якою я працював, де стандарт кодування, що забороняв раннє повернення, мав функції, всіяні булевими змінними (з такими іменами continue_processing), які контролювали виконання блоків коду далі внизу функції.
Грег Хьюгілл

21
Що таке RIAA? Це щось подібне до RAII? = D
jkeys

1
Залежить, скільки у нього петель і наскільки глибоко йде гніздо ... Ви хочете блакитну або червону таблетку?
Метт

34

Розрив буде виходити лише з внутрішньої петлі, що містить його.

Ви можете використовувати goto для виходу з будь-якої кількості петель.

Звичайно, гото часто вважається шкідливим .

чи правильно використовувати функцію перерви [...]?

Використання перерви та переходу може ускладнити міркування про правильність програми. Дивіться тут дискусію з цього приводу: Дейкстра не був божевільним .


16
Хороша відповідь у тому, що вона пояснює, що мем "goto шкідливий" сильно прив'язаний до більш узагальненого твердження "переривання потоку управління шкідливе". Безглуздо говорити "гото шкідливо", а потім розвернутися і рекомендувати використовувати breakабо return.
Павло Мінаєв

6
@Pavel: breakі returnмати перевагу перед тим, gotoщо вам не потрібно полювати на етикетку, щоб знайти, куди вони йдуть. Так, під ними вони є якимись goto, але дуже обмеженими. Їх набагато простіше розшифрувати за допомогою мозку програміста, що відповідає зразкам, ніж необмежений goto. Тож ІМО вони переважніші.
sbi

@sbi: Правда, але перерва все ще не є частиною структурованого програмування. Це просто краще переноситься, ніж а goto.
jkeys

2
@KarlVoigtland посилання Dijkstra застаріло; це, здається, працює: blog.plover.com/2009/07
Аарон Брагер

3
З використанням goto в цій ситуації абсолютно нічого поганого. Добре розміщений гото - це стрибки і межі, які краще і легше для читання, ніж багато запропонованих рішень, що пропонуються інакше.
Джеймс

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
що добре, але все ще не настільки читабельний, я б вважав за краще перейти до цього випадку
Петър Петров

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
цікавий підхід, але мені, безумовно, подобається, як еред @ inf.ig.sh справляється з ним. for (без підписаного int y = 0; y <y_max &&! gotoMainLoop; y ++).
Енді

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 змінних для складання кадру, що обрамляє цю функцію.
Петър Петров

1
@ ПетърПетров, тоді йдіть на лямбда, що також краще, оскільки ви можете точно визначити її там, де вам це потрібно.
ДаріоП

+1 для лямбдів, але капітальний ремонт в ядрі ігрового двигуна, де навіть одна рамка стека все ще є вузьким місцем. Вибачте, але лямбди не такі легкі, як мінімум, у MSVC 2010.
Петър Петров

@ ПетърПетров Потім змініть пару функцій на клас, а змінні стека - на приватні члени.
Артур Такка

Це лише ускладнює і без того складний код :) Іноді goto - єдине рішення. Або якщо ви пишете складні автомати з документацією "goto state X", тоді goto фактично робить код прочитаним, як написано в документі. Крім того, обидва C # і go мають гото з ціллю: жодна мова не є повною мірою без goto, а gotos часто є найкращим інструментом для написання проміжного перекладача або схожого на збірку коду.
Петър Петров

5

goto може бути дуже корисним для розбиття вкладених циклів

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

"Продовжити" там не працюватиме, оскільки ітераційний вираз першого циклу не буде виконаний
Фабіо А.

Я припускаю, що ітератор для першого циклу є i. Звідси i++перед
гото

0

Інші мови, такі як PHP, приймають параметр break (тобто перерва 2;), щоб вказати кількість вкладених рівнів циклу, з яких ви хочете вийти, C ++, однак, не робить. Вам доведеться розробити це, використовуючи булевий сигнал, який ви встановили на помилкове перед циклом, встановлене на істинному циклі, якщо ви хочете перервати, плюс умовний перерву після вкладеного циклу, перевіривши, чи встановлено булеве значення true і зламати, якщо так.


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.
Сергій

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 Рішення має голоси, оскільки воно використовує виняток для управління потоком виконання. Як випливає з назви, винятки слід використовувати лише у виняткових випадках.
Хеліо Сантос

4
@HelioSantos Це не виняткова ситуація, для якої мова не забезпечує належного рішення?
hkBattousai

8
Винятки - повільні.
Гордон

2
Ефект від удару кидка величезний для чогось, що не є непоправною помилкою 99% часу.
Michaël Roy

-4

Вихід із циклу for-циклу для мене трохи дивний, оскільки семантика for-loop зазвичай вказує на те, що він буде виконаний задану кількість разів. Однак це не погано у всіх випадках; якщо ви шукаєте щось у колекції і хочете зламати, коли знайдете, це корисно. Виривання вкладених циклів, однак, неможливо в C ++; це в інших мовах через використання міченої перерви. Можна використовувати етикетку та гото, але це може спричинити печію вночі ..? Схоже, найкращий варіант.


11
Це зовсім не дивно. Якщо ви перебираєте колекцію, щоб шукати щось (і не маєте більш швидкого способу пошуку), немає сенсу закінчувати цикл. (як один приклад)
Джо
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.