Ответ 1
используйте goto. он чист и прост.
Предположим, мне нужно вырваться из трех или четырех вложенных циклов сразу при появлении какого-либо события внутри самого внутреннего цикла. Что такое аккуратный способ сделать это?
то, что я делаю, это использовать флаги следующим образом:
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;
}
Я не думаю, что это особенно опрятно.
Как бы вы выполнили одно и то же? (без использования переходов)
используйте goto. он чист и прост.
Поместите все петли в функцию и просто верните, а не перерыв.
Если вы используете Java, вы можете связывать метки с каждым для блока, а затем ссылаться на метку после инструкции continue. Например:
outerfor:
for (int i=0; i<5; i++) {
innerfor:
for (int j=0; j<5; j++) {
if (i == 1 && j == 2) {
continue outerfor;
}
}
}
Как бы вы выполнили одно и то же? (без использования переходов)
Почему? Ничто не является повсеместно злым, и каждый наложенный инструмент использует его (кроме 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:
Гораздо чище, чем все эти переменные флага, и он даже более четко показывает, что делает ваш код.
Просто немного лучше.
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;
}
}
}
}
Но если вам действительно нужно иметь эти циклы, тогда имеет смысл явно декларировать в каждом цикле, какие условия должны быть выполнены для продолжения, для удобства чтения.
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;
}
}
}
}
Идея заключается в том, что я получаю гарантированный бесплатный балл, плюс я получаю чистый двухуровневый выход из коммутатора через возврат.
Если вы абсолютно не хотите использовать 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 в прыжке в конец самого внешнего цикла
иногда вы можете использовать трюк следующим образом:
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;
}
это стоит только строка, но чистая, я думаю.
джастин
Если преждевременное завершение любого цикла всегда означает, что вы также должны разбить замкнутый цикл, тогда вам не нужны дополнительные флаги. Все это может выглядеть следующим образом
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;
}
По моему опыту, это то, что необходимо в большинстве случаев.
немного глупо самодокументирующегося:
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;
}
но на самом деле у вас не должно быть трех вложенных циклов. Вы должны рассмотреть возможность реорганизации в разные функции и улучшить свою программную логику вместо этого.
Хотя я согласен, что иногда goto
действительно лучшее решение, я думаю, что любая проблема, к которой goto
является решением, является результатом плохого кода.
Разделение на 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
, которое происходит на таких событиях, оставлено как упражнение для читателя (это тривиально).
Я бы сделал что-то вроде:
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;
}
}
}
}
Если вы используете 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);
}
}
}
}
Один из способов сделать это - конечный автомат. Но я все равно буду использовать 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;
}
}