Могу ли я использовать break для выхода из нескольких вложенных циклов for?


304

Можно ли использовать break функцию для выхода из нескольких вложенныхfor циклов?

Если так, как бы вы поступили? Можете ли вы также контролировать, сколько петель breakвыходов?


1
Вместо использования break или goto для выхода из нескольких вложенных циклов вы можете заключить эту конкретную логику в функцию и использовать return для выхода из нескольких вложенных циклов. Это сохранит эстетику вашего кода и предотвратит использование goto, что является плохой практикой программирования.
Ришаб Шингал

Ответы:


239

AFAIK, C ++ не поддерживает циклы именования, как Java и другие языки. Вы можете использовать goto или создать значение флага, которое вы используете. В конце каждого цикла проверьте значение флага. Если установлено значение true, вы можете выйти из этой итерации.


317
Не бойтесь использовать, gotoесли это лучший вариант.
jkeys

18
Я новый программист на C ++ (и без какого-либо формального обучения программированию), поэтому после прочтения информации о людях на goto. Я не решаюсь использовать его в страхе, что моя программа может внезапно взорваться и убить меня. Кроме этого, когда я использовал для написания программ на своем ti-83 (конечно, на уроке скучной математики), функции, предоставляемые основным редактором, требовали использования goto's.
Faken

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из этого?
ВОО

265

Нет, не портите это break. Это последний оставшийся оплот для использования goto.


19
Стандарт кодирования MISRA C ++ позволяет использовать goto для решения именно такой ситуации.
Ричард Корден

11
Настоящие программисты не боятся использовать goto. Сделал это за 25 лет - не жалею - сэкономил массу времени на разработку.
Даг Налл

1
Я согласен. gotos является обязательным, если у вас нет 8K пикселей в ширину, и вам нужно вызвать последовательность из 30 вызовов ОС Windows.
Микаэль Рой

Или просто «вернуть», поместив код в функцию.
Тара

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 (он же видимости gaurd). Это не увеличит производительность, но может улучшить читабельность и снизить риск пропуска скобок вызова функции.
Red.Wave

56

Другой подход к разрыву вложенного цикла состоит в том, чтобы выделить оба цикла в отдельную функцию, и return из этой функции, когда вы хотите выйти.

Конечно, это приводит к другому аргументу о том, следует ли вам когда-либо явно returnиз функции где-либо, кроме как в конце.


7
Это проблема С. С RIAA досрочное возвращение не является проблемой, так как все проблемы, связанные с досрочным возвратом, решаются правильно.
Мартин Йорк,

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

21
Что такое RIAA? Это что-то вроде RAII? = D
jkeys

1
Зависит от того, сколько у него петель и насколько глубоко гнездо уходит ... Вы хотите голубую таблетку или красную таблетку?
Мэтт

34

перерыв будет выходить только из самого внутреннего цикла, содержащего его.

Вы можете использовать GoTO чтобы выйти из любого числа циклов.

Конечно, Гото часто считается вредным .

Правильно ли использовать функцию прерывания [...]?

Использование break и goto может затруднить рассуждение о правильности программы. Смотрите здесь для обсуждения этого: Дейкстра не была безумной .


16
Хороший ответ состоит в том, что он объясняет, что мем "goto is вреден" сильно связан с более обобщенным утверждением "прерывание потока управления вредно". Бессмысленно говорить «goto вредно», а затем повернуться и порекомендовать использовать breakили return.
Павел Минаев

6
@Pavel: breakи returnимейте преимущество перед тем, gotoчто вам не нужно охотиться за лейблом, чтобы найти, куда они идут. Да, под ними какие-то goto, но очень ограниченные. Их гораздо легче расшифровать с помощью мозга, сопоставляющего шаблоны, чем неограниченный goto. Так что имо они предпочтительнее.
ВОО

@sbi: Да, но разрыв все еще не является частью структурированного программирования. Это просто лучше переносится чем goto.
jkeys

2
@KarlVoigtland ссылка Dijkstra устарела; похоже, это работает: blog.plover.com/2009/07
Аарон Брагер,

3
Нет ничего плохого в использовании goto в этой ситуации. Хорошо поставленное 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
это хорошо, но все же не так читабельно, в этом случае я бы предпочел перейти на 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 справляется с этим. 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
@ ПетърПетров, тогда иди к лямбде, которая тоже лучше, так как ты можешь определить, где именно она тебе нужна.
DarioP

+1 за лямбды, но капитальный ремонт в ядре игрового движка, где даже один кадр стека все еще является узким местом. Извините, но лямбды не такие легкие, по крайней мере, в MSVC 2010.
Петър Петров

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

Это только усложняет и без того сложный код :) Иногда goto является единственным решением. Или, если вы пишете сложные автоматы с документацией «goto state X», то goto фактически делает код читаемым, как написано в документе. Кроме того, и в C #, и в go есть goto с целью: ни один язык не является тьюринг-полным без 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справедливо в следующих обстоятельствах:

Чтобы смоделировать break/ continue, вы хотите:

Перерыв

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

Продолжать

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

«Продолжить» не будет работать там, потому что итерационное выражение первого цикла не будет выполнено
Фабио А.

Я предполагаю, что итератор для первого цикла i. Следовательно, i++до перехода
JuliusAlphonso

0

Другие языки, такие как PHP, принимают параметр для разрыва (то есть, для разрыва 2;), чтобы указать количество уровней вложенных циклов, из которых вы хотите разорвать, однако C ++ этого не делает. Вы должны будете решить это, используя логическое значение, которое вы установили в false перед циклом, установите значение true в цикле, если вы хотите разорвать, плюс условный разрыв после вложенного цикла, проверяя, было ли логическое значение установлено в 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% случаев.
Микаэль Рой

-4

Выход из цикла for немного странен для меня, поскольку семантика цикла for обычно указывает на то, что он будет выполняться определенное количество раз. Тем не менее, это не плохо во всех случаях; если вы ищете что-то в коллекции и хотите сломать ее после того, как найдете ее, это полезно Однако выход из вложенных циклов в C ++ невозможен; это на других языках с помощью помеченного перерыва. Вы можете использовать ярлык и перейти, но это может вызвать у вас изжогу ночью ..? Похоже, лучший вариант, хотя.


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