Каков удобный способ разорвать сразу несколько циклов for?

Предположим, мне нужно сразу выйти из трех или четырех вложенных циклов for при возникновении некоторого события внутри самого внутреннего цикла. Как это сделать аккуратно?

я использую флаги вот так:

int i, j, k;
int flag1 = 0;
int flag2 = 0;

for (i = 0; i < 100; i++) {
    for (j = 0; j < 100; j++) {
        for (k = 0; k < 100; k++) {
            if (k == 50) {
                flag1 = 1;
                flag2 = 1;
                break;
            }
        }
        if (flag1 == 1)break;
    }
    if (flag2 == 1)break;
}

Не думаю, что это особенно аккуратно.

Как бы вы сделали то же самое? (без прыжков)


person Moeb    schedule 19.10.2009    source источник
comment
какой язык? .   -  person newacct    schedule 19.10.2009
comment
я имел в виду C, когда я писал вопросы ... какой язык поддерживает это лучше?   -  person Moeb    schedule 19.10.2009
comment
Мне нравится Environment.FailFast (string.Empty); . Он быстро и красиво выйдет из циклов без всяких лишних вещей ... :)   -  person KristoferA    schedule 19.10.2009
comment
В PHP break может принимать количество вложенных циклов, которые вы хотите выйти: php.net /manual/en/control-structures.break.php. Тот же принцип применяется для продолжения: php.net/manual/en/control -structures.continue.php.   -  person sergiol    schedule 03.03.2013


Ответы (14)


используйте goto. это чисто и просто.

person Test    schedule 19.10.2009
comment
Шутки в сторону? Вы когда-нибудь слышали о книге Эдсгера Дейкстры "Считаться вредной" - en.wikipedia.org/wiki/Considered_harmful - person Asaph; 19.10.2009
comment
Так много людей думают, что связка флагов в чем-то лучше, чем goto, просто из-за какой-то риторики, которую они когда-то слышали. - person John La Rooy; 19.10.2009
comment
@Asaph, они обсуждают чрезмерное использование goto неструктурированным способом. - person John La Rooy; 19.10.2009
comment
@Asaph - Вы когда-нибудь слышали о фанатизме? Дийкстра сказал: «Вредно», а не «Отродье сатаны». Вы полностью упускаете его точку зрения. - person Chris Lutz; 19.10.2009
comment
если код такой же простой, как указано выше, то goto приятный и читаемый, и я полностью за него. Но если это так просто, как указано выше, то это можно сделать всего за 1 цикл. - person moogs; 19.10.2009
comment
@hanifr. По крайней мере, прочтите всю статью Дейкстры перед тем, как списать гото. - person John La Rooy; 19.10.2009
comment
@hanifr - Что плохого в простоте, чистоте, ясности и удобочитаемости? Обычно goto не ничего из этих вещей, но время от времени он становится идеальным решением вашей проблемы. И почти в каждом другом языке есть особые случаи использования встроенных ключевых слов, таких как continue или next, чтобы скрыть то, что по сути является этим типом goto, за более красивым и более управляемым видом, который идентичен тому, что делают операторы if и while и for. goto вот это в любом случае неплохо - person Chris Lutz; 19.10.2009
comment
+1 это единственное место, где я бы поставил java точку по сравнению с C ++ или C #. В Java есть оператор break <label>, который прерывает цикл с определенной меткой. Ни C, ни C # не имеют ничего похожего, поэтому единственный чистый способ сделать это - использовать goto. И @ asaph, время от времени думай про себя, не повредит! - person Blindy; 19.10.2009
comment
Ага, goto это так. Меня часто удивляет то, на что некоторые люди готовы пойти, чтобы избежать написания этого проклятого ключевого слова, даже если оно явно является лучшим - например, наиболее очевидным, наиболее читаемым, наиболее удобным в обслуживании - решением проблемы. - person Pavel Minaev; 19.10.2009
comment
Согласен - у goto есть свое место. Просто держите его там, где он действительно помогает, а не мешает. - person Michael Burr; 19.10.2009
comment
goto на победу. Реакционное отношение Kneejerk к языковому ключевому слову просто смешно. - person Carl Norum; 19.10.2009
comment
Поместите его как функцию и используйте возврат. По сути, похоже на разрыв, но я думаю, что это намного более чистый код. - person Jeremy; 19.10.2009
comment
Флаги ‹Перейти‹ Рефакторинг + Функции + возврат. - person GManNickG; 19.10.2009
comment
+1 за goto. goto не является злом при правильном использовании, хотя быть либеральным с ним, вероятно, плохо ... - person Chinmay Kanchi; 19.10.2009
comment
Я прочитал вопрос и был готов ответить goto, хотя я бы поставил его в теге ‹irony› :) Мне лично здесь больше нравятся флаги, почему? Потому что с помощью goto вы нарушаете поток кода и не можете вызвать деструктор или какой-либо другой важный метод. - person cwap; 19.10.2009
comment
Если кому-то это нужно, вот ссылка на статью Кнута о структурированном программировании с переходом к выражениям pplab.snu.ac.kr/courses/adv_pl05/papers/p261-knuth.pdf - person bbg; 19.10.2009

Поместите все циклы в функцию и просто вернитесь вместо break.

person Foole    schedule 19.10.2009
comment
Обратите внимание, что, хотя это аккуратно избегает явного goto, оно по сути делает то же самое, поэтому, если вы думаете, что это хорошее решение, вы действительно не можете возражать против решения goto. И обратите внимание, что goto может быть предпочтительнее в случае сложного поведения цикла, которое может сделать вызов функции менее понятным или простым. - person Chris Lutz; 19.10.2009
comment
Разница в том, что вы не можете злоупотреблять возвратом так же, как и goto. Между прочим, в Java есть помеченный оператор break, который позволяет выходить из вложенных циклов, но не goto. - person starblue; 19.10.2009
comment
В C ++ return лучше, потому что он учитывает раскручивание стека и, таким образом, уничтожает любые переменные с локальной областью видимости, объявленные в любом из циклов. - person philsquared; 19.10.2009
comment
Я определенно предпочел бы подход возврата к goto, поскольку, по моему опыту, он имеет тенденцию создавать более чистый общий код. Не потому, что этот goto-s не может быть чистым, но если вы используете return, вы также изолировали функцию от одной ответственности, выполняя цикл. Если вы используете gotos, вы, вероятно, помещаете больше отдельных задач в одну и ту же функцию. Конечно, это не универсальная истина, и, возможно, в данном случае это не так, но это была бы моя общая позиция. - person Pete; 19.10.2009
comment
По сути, они могут быть одним и тем же, но с точки зрения кода, я думаю, что return намного чище. - person Jeremy; 19.10.2009
comment
Согласен, это правильное решение. goto - второе место, но если у вас есть три вложенных цикла for и вам нужно выйти из него, вам необходимо провести рефакторинг вашего кода. - person GManNickG; 19.10.2009
comment
У Return есть дополнительное преимущество, заключающееся в переносе значения, которое мы вышли из-за условия X. С goto вам нужно будет установить для него некоторую флаговую переменную и помнить, что существует переменная. - person che; 19.10.2009

Если вы используете Java, вы можете связать метки с каждым блоком for, а затем ссылаться на метку после оператора continue. Например:

outerfor:
for (int i=0; i<5; i++) {
    innerfor:
    for (int j=0; j<5; j++) {
        if (i == 1 && j == 2) {
             continue outerfor;
        }
    }
}
person Asaph    schedule 19.10.2009
comment
Многие языки (например, Perl) допускают такое поведение меток, что хорошо, и его следует использовать там, где это возможно. Так что я бы +1, если бы это относилось к C. (я знаю, что OP интересуется другими языками, но, вероятно, его больше интересует C.) - person Chris Lutz; 19.10.2009
comment
@Chris Lutz: достаточно честно. FWIW: при первоначальной публикации ОП не было упоминания о том, какой язык он / она использует. Отсюда все комментарии с вопросом на каком языке. Тег c был добавлен после того, как я опубликовал. - person Asaph; 19.10.2009

Как бы вы сделали то же самое? (без прыжков)

Почему? Ничто не является универсальным злом, и каждый наложенный инструмент имеет свое применение (кроме gets()). Использование goto здесь делает ваш код более чистым, и это один из единственных вариантов, которые у нас есть (при условии C). Смотреть:

int i, j, k;

for (i = 0; i < 100; i++) {
    for (j = 0; j < 100; j++) {
        for (k = 0; k < 100; k++) {
            if (k == 50) {
                goto END;
            }
        }
    }
}
END:

Намного чище, чем все эти флаговые переменные, и даже более четко показывает, что ваш код делает.

person Chris Lutz    schedule 19.10.2009
comment
И как бы вы это реорганизовали? Вы НЕ ДЕЙСТВИТЕЛЬНО хотите, чтобы в вашем коде было 3 вложенных цикла? - person Bostone; 19.10.2009
comment
Я не знаю, как это реорганизовать, потому что OP не очень конкретен в отношении того, что его три вложенных цикла делают в его коде. Он просто хочет вырваться из них всех изнутри одного из них, поэтому я даю ответ. Для выполнения некоторых операций требуется O (n ^ 3) времени, но на практике они оказываются достаточно быстрыми, поскольку они обычно работают с небольшими наборами данных и / или выполняемая операция проста и легко оптимизируется. Не пытайтесь реорганизовать то, что ясно и чисто и работает нормально. - person Chris Lutz; 19.10.2009

Просто немного лучше.

int i, j, k;
int flag1 = 0;
int flag2 = 0;

for (i = 0; i < 100 && !flag2; i++) {
    for (j = 0; j < 100 && !flag1; j++) {
        for (k = 0; k < 100; k++) {
            if (k == 50) {
                flag1 = 1;
                flag2 = 1;
                break;
            }
        }
    }
}

Но если вам действительно нужны эти циклы, для удобства чтения имеет смысл явно указывать в каждом цикле, какие условия должны выполняться для его продолжения.

person moogs    schedule 19.10.2009
comment
На мой взгляд, это не совсем лучше. Огромный объем информации, помещаемой в оператор for, начинает выглядеть как шум строки. - person Chris Lutz; 19.10.2009
comment
Какой смысл иметь несколько флагов? Просто возьми. Назовите это done. Намного понятнее. - person Alan; 19.10.2009
comment
просто скопировал образец. Я предполагаю, что флаги не так просты и просто представляют фактический код. - person moogs; 20.10.2009
comment
опять же, если это так просто, как то, что он сказал (немедленно вспыхивает). ты прав - person moogs; 20.10.2009

goto. Это одно из очень немногих мест, где goto является подходящим инструментом, и обычно это аргумент, почему goto не является полным злом.

Но иногда я делаю так:

void foo() {
    bar_t *b = make_bar();
    foo_helper(bar);
    free_bar(b);
}

void foo_helper(bar_t *b) {
    int i,j;
    for (i=0; i < imax; i++) {
        for (j=0; j < jmax; j++) {
            if (uhoh(i, j) {
                return;
            }
        }
    }
}

Идея состоит в том, что я гарантированно получаю бесплатный бар, плюс я получаю чистый двухуровневый выход из переключателя через возврат.

person ts4z    schedule 19.10.2009

Если вы абсолютно не хотите использовать goto, установите для всех условий цикла значение false:

int i, j, k;

for (i = 0; i < 100; i++) {
    for (j = 0; j < 100; j++) {
        for (k = 0; k < 100; k++) {
            if (k == 50) {
                i = j = k = INT_MAX;
                break;
            }
        }
    }
}

примечание: интеллектуальный оптимизирующий компилятор превратит содержимое if при переходе в конец самого внешнего цикла

person CAFxX    schedule 18.08.2011

иногда можно использовать такой трюк:

for (i = 0; i < 100 && !flag2; i++) {
for (j = 0; j < 100 && !flag1; j++) {
    for (k = 0; k < 100; k++) {
        if (k == 50) {
            k = 100;
            i = 100;
            j = 100;
        }
    }
}

}

или объявите флаг сложения в своем цикле:

bool end = false;
for(int i =0; i < 1000 && !end; i++) {
   //do thing
   end = true;
}

он стоит всего одну строчку, но чистый, я думаю.

Джастин

person thethanghn    schedule 19.10.2009

Если преждевременное завершение любого цикла всегда означает, что вы также должны прервать окружающий цикл, то вам не нужны дополнительные флаги. Все это может выглядеть следующим образом

int i, j, k;
for (i = 0; i < 100; i++) {
    for (j = 0; j < 100; j++) {
        for (k = 0; k < 100; k++) {
            if (k == 50)
                break;
        }
        if (k < 100) break;
    }
    if (j < 100) break;
}

По моему опыту, это то, что нужно в большинстве случаев.

person AnT    schedule 19.10.2009

немного глупого самодокументирования:

int i, j, k;
int done = 0;

for (i = 0; i < 100 && ! done; i++) {
    for (j = 0; j < 100 && ! done; j++) {
        for (k = 0; k < 100 && ! done; k++) {
            if (k == 50) we_are(done);
        }
    }
}

//...

void we_are(int *done) {
    *done = 1;
}

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

Хотя я согласен с тем, что иногда goto действительно является лучшим решением, я думаю, что любая проблема, для которой goto является решением, является результатом плохого кода.

person Carson Myers    schedule 19.10.2009

Деление на 0 - это самый надежный из известных мне методов, который вырвет вас из любого количества циклов. Это работает, потому что инструкция по сборке DIV не любит такой глупости.

Итак, вы можете попробовать это:

int i, j, k;
int flag1 = 0;
int flag2 = 0;

for (i = 0; i < 100; i++) {
    for (j = 0; j < 100; j++) {
        for (k = 0; k < 100; k++) {
            if (k == 50) {
                flag1 = 1;
                flag2 = 1;
                int z = 1 / 0;  // we're outta here!!!
            }
        }
        if (flag1 == 1)break;
    }
    if (flag2 == 1)break;
}

Возвращение к trap, которое случается с такими событиями, оставлено в качестве упражнения для читателя (это тривиально).

person DV.    schedule 19.10.2009
comment
Достаточно хороший компилятор не должен компилировать это (поскольку это недопустимое постоянное выражение), кроме этого исключения никогда не должны использоваться в качестве замены управляющей логики. Советовать людям делать это кажется мне ДИВИЛЬНЫМ :-) - person jdehaan; 19.10.2009
comment
Я голосую за это, потому что это так забавно. - person Justicle; 16.02.2010

Я бы сделал что-то вроде:

  int i, j, k;

  for (i = 0; i < 100; i++) {
      for (j = 0; j < 100; j++) {
          for (k = 0; k < 100; k++) {
              if (k == 50) {
                  return;
              }
          }
      }
  }
person Paul    schedule 19.10.2009

Если вы используете GCC и эту библиотеку, break может принимать желаемое количество вложенных циклов. выходить:

int i, j, k;

for (i = 0; i < 100; i++) {
    for (j = 0; j < 100; j++) {
        for (k = 0; k < 100; k++) {
            if (k == 50) {
                break(3);
            }
        }
    }
}
person DaBler    schedule 22.12.2017

Один из способов сделать это - конечный автомат. Но я бы все равно использовал goto. Все намного проще. :)

state = 0;
while( state >= 0){
    switch(state){
        case 0: i = 0; state = 1; // for i = 0
        case 1:
            i++; 
            if (i < 100)   // if for i < 100 not finished
                state = 2; // do the inner j loop
            else
                state = -1; // finish loop
        case 2: j = 0; state = 3; // for j = 0
        case 3: 
            j++;
            if (j < 100)  // if j < 100 not finished
                state = 4 // do the inner k loop
            else
                state = 1; // go backt to loop i
            break;
        case 4: k = 0; state = 5;
        case 5:
            k++;
            if (k == 50){
                state = -1;
                break;
            }
            if (k < 100) // if k loop not finished
                state = 5; // do this loop
            else
                state = 3; // go back to upper loop
            break;
        default : state = -1;
    }
}
person Community    schedule 19.10.2009
comment
В чем преимущество конечного автомата перед goto? - person Alphaneo; 19.10.2009
comment
На самом деле нет. Это просто еще один способ выполнения этих циклов и выхода без goto. Я бы сделал это с помощью goto. В некоторых случаях лучше использовать это. Например, если у вас много прыжков из одного цикла в другой, например regExp. Парсеры RegExp компилируют эти машины, потому что они работают быстро и их можно оптимизировать. - person Egon; 19.10.2009