Могу ли я использовать break для выхода из нескольких вложенных циклов?
Можно ли использовать break
функция выхода из нескольких вложенных for
петли? Если так, как бы вы поступили? Можете ли вы также контролировать, сколько петель выходит из перерыва?
24 ответа
AFAIK, C++ не поддерживает циклы именования, как Java и другие языки. Вы можете использовать goto или создать значение флага, которое вы используете. В конце каждого цикла проверьте значение флага. Если установлено значение true, вы можете выйти из этой итерации.
Нет, не портите это break
, Это последний оставшийся оплот для использования goto
,
Просто чтобы добавить явный ответ, используя лямбды:
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, но я думаю, что он весьма полезен.
Другой подход к выходу из вложенного цикла состоит в том, чтобы выделить оба цикла в отдельную функцию, и return
из этой функции, когда вы хотите выйти.
Конечно, это поднимает другой аргумент того, следует ли вам когда-либо явно return
из функции в любом месте, кроме в конце.
break будет выходить только из самого внутреннего цикла, содержащего его.
Вы можете использовать goto, чтобы выйти из любого числа циклов.
Конечно, Гото часто считается вредным.
Правильно ли использовать функцию прерывания [...]?
Использование break и goto может усложнить рассуждения о правильности программы. Смотрите здесь для обсуждения этого: Дейкстра не была безумной.
Как насчет этого?
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;
}
}
}
}
Пример кода с использованием goto
и метка для выхода из вложенного цикла:
for (;;)
for (;;)
goto theEnd;
theEnd:
Хотя этот ответ уже был представлен, я думаю, что хороший подход заключается в следующем:
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;
}
}
}
Хороший способ вырваться из нескольких вложенных циклов - это преобразовать код в функцию:
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;
}
}
}
}
Я знаю, что это старая ветка, но я чувствую, что об этом действительно нужно сказать, и мне больше не о чем говорить. Для всех здесь используйте goto. Я просто использовал это.
Как и почти все, goto не на 100% или /xor "плохо" или "хорошо". Есть как минимум два использования, где я бы сказал, что если вы используете для них goto - и не используете его для чего-либо еще - вы не только должны быть на 100% в порядке, но и ваша программа будет даже более читабельной, чем без него, поскольку это делает ваше намерение намного яснее (есть способы избежать этого, но я обнаружил, что все они намного более неуклюжие):
- Выход из вложенных циклов и
- Обработка ошибок (например, переход к процедуре очистки в конце функции, чтобы вернуть код ошибки и освободить память.).
Вместо того, чтобы просто догматично принимать правила вроде "так себе - зло", поймите, почему такое мнение заявлено, и следуйте "почему", а не букве сантимента. Незнание этого тоже доставило мне массу неприятностей, я бы даже сказал, что называть вещи догматически "злом" может быть более вредным, чем сама вещь. В худшем случае вы просто получаете плохой код - и тогда вы знаете, что не использовали его правильно, пока слышали, что нужно быть осторожным, но если вы ломаете себя, пытаясь удовлетворить догматизм, я бы сказал, что это хуже.
Почему goto называется "злом", потому что вы никогда не должны использовать его для замены обычных if, for и while. А почему это? Попробуйте, попробуйте все время использовать "goto" вместо обычных операторов логики управления, затем попробуйте написать тот же код еще раз с логикой управления и скажите мне, какой из них выглядит лучше и понятнее, а какой больше похож на беспорядок. Вот так. (Бонус: попробуйте добавить новую функцию сейчас в код только для goto.) Вот почему это "зло" с подходящей квалификацией области действия вокруг "зла". Используя его для короткого замыкания недостатков C "break
"команда не является проблемным использованием, пока вы ясно даете понять из кода, что должен выполнять ваш goto (например, используя метку типа"nestedBreak"или что-то в этом роде). Выход из вложенного цикла очень естественен.
(Или, проще говоря: используйте goto для выхода из цикла. Я бы сказал, что это даже предпочтительнее. Не используйте goto для создания цикла. Это "зло".)
А как узнать, что вы догматичны? Если следование правилу "xyz - зло" приводит к тому, что ваш код становится менее понятным, потому что вы искажаете себя, пытаясь обойти его (например, добавляя дополнительные условия в каждый цикл, или некоторую переменную флага, или какой-либо другой подобный трюк), то вы, скорее всего, будете догматичны.
Там не заменит для обучения хорошие мыслительные привычки, чем хорошие в большей степени, кодирования привычки. Первые предшествуют последним, а последние часто будут следовать после принятия первых. Проблема, однако, в том, что я слишком часто нахожу, что последние недостаточно объяснены. Слишком многие просто говорят "это плохо" и "это нужно больше мысли", не говоря, что думать, что думать о, и почему. И это большой позор.
(FWIW, в C++ необходимость выхода из вложенных циклов все еще существует, но необходимости в кодах ошибок нет: в этом случае всегда используйте исключения для обработки кодов ошибок, никогда не возвращайте их, если только это не будет настолько частым, что исключение и перехват вызовут проблемы с производительностью, например, в замкнутом цикле в коде сервера с высокими требованиями, возможно [некоторые могут сказать, что "исключения" следует "использовать редко", но это еще одна часть непродуманного догматизма: нет, по крайней мере, по моему опыту, после отказа от этой догмы я обнаружил, что они делают вещи намного яснее - просто не злоупотребляйте ими, чтобы делать что-то, кроме обработки ошибок, например, использовать их как поток управления; фактически то же самое, что и с "goto". вы используете их все и только для обработки ошибок, вот для чего они нужны.].
Я не уверен, стоит ли оно того, но вы можете эмулировать именованные циклы 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);
}
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
Я думаю goto
действителен при таких обстоятельствах:
Для имитации break
/continue
Вы бы хотели:
Перерыв
for ( ; ; ) {
for ( ; ; ) {
/*Code here*/
if (condition) {
goto theEnd;
}
}
}
theEnd:
Продолжить
for ( ; ; ) {
for ( ; ; ) {
/*Code here*/
if (condition) {
i++;
goto multiCont;
}
}
multiCont:
}
break
оператор прекращает выполнение ближайшего вложенияdo
,for
,switch
, или жеwhile
утверждение, в котором оно появляется. Управление переходит к оператору, который следует за завершенным оператором.
из MSDN.
Любое количество петель разбить на одну 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;
все петли.
Мое предложение - использовать контрольную переменную, чтобы разорвать нужный цикл. Код результата может быть не таким приятным.
Вы можете использовать препроцессоры, чтобы сделать желаемый взлом под капотом. Такой подход может скрыть уродливые коды и дополнительную сложность.
Например, я создал свой собственный механизм прерывания следующим образом:
Разыскиваемый код:
for (int i = 0; i < 100; i++) {
for (int j = 0; j < 100; j++) {
for (int k = 0; k < 100; k++) {
//do something
if (desiredCondition) {
breakToLevel = 0;
}
if (breakToLevel < 3) {
break;
}
}
if (breakToLevel < 2) {
break;
}
}
if (breakToLevel < 1) {
break;
}
}
Определенные макросы:
#define BREAK_TO(L) breakToLevel = (L);
#define CHECK_BREAK(L) if (breakToLevel < (L)) break;
и результат:
for (int i = 0; i < 100; i++) {
for (int j = 0; j < 100; j++) {
for (int k = 0; k < 100; k++) {
//do something
if (desiredCondition) {
BREAK_TO(0)
}
CHECK_BREAK(3)
}
CHECK_BREAK(2)
}
CHECK_BREAK(1)
}
bool found = false;
for(int i=0; i < m; ++i){
for(int j=0; j < n; ++j)
if(grid[i][j] == '*'){
q.push(make_pair(i,j));
found = true;
break;
}
if(found)
break;
}
Другие языки, такие как PHP, принимают параметр для разрыва (то есть, для разрыва 2;), чтобы указать количество уровней вложенных циклов, из которых вы хотите разорвать, однако C++ не делает этого. Вам нужно будет решить эту проблему, используя логическое значение, которое вы установили в false перед циклом, установите в цикле значение true, если вы хотите разорвать, плюс условный разрыв после вложенного цикла, проверяя, было ли логическое значение установлено в true и сломаться, если да.
Самый изящный способ — использовать исключение .
Исключение означает возникновение ненормальной ситуации, которая может быть не только слишком плохой, но и слишком хорошей.
Например, вы ищете элемент в трехмерном массиве с тремя вложенными циклами for. Обычная ситуация: «Ток НЕ тот , который мне нужен». Ненормальная ситуация : «Текущая ЕСТЬ та самая».
Помните разницу между ошибкой и исключением . Исключение не обязательно должно быть ошибкой, это может быть хорошей новостью.
Я знаю, что это старый пост. Но я бы предложил немного логичный и более простой ответ.
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;
}
}
}
while (i<n) {
bool shouldBreakOuter = false;
for (int j=i + 1; j<n; ++j) {
if (someCondition) {
shouldBreakOuter = true;
}
}
if (shouldBreakOuter == true)
break;
}
вы можете использовать «goto», чтобы оставить вложенные циклы ниже, это мой исходный код, включая «goto»
int main()
{
string str;
while (cin >> str)
{
if (str == "0")
break;
int sum = 0;
for (auto ch : str)
{
if (ch <= 'z' && ch >= 'a')
sum += (ch - 'a' + 1);
else if (ch >= 'A' && ch <= 'Z')
sum += (ch - 'A' + 1);
else
{
cout << "Fail" << endl;
goto fail;
}
}
cout << sum << endl;
fail:
}
return 0;
}
однако я мог бы избежать "перейти", добавив функцию "вычислить"
void calculate(const string &str)
{
int sum = 0;
for (auto ch : str)
{
if (ch <= 'z' && ch >= 'a')
sum += (ch - 'a' + 1);
else if (ch >= 'A' && ch <= 'Z')
sum += (ch - 'A' + 1);
else
{
cout << "Fail" << endl;
return;
}
}
cout << sum << endl;
}
int main()
{
string str;
while (cin >> str)
{
if (str == "0")
break;
calculate(str);
}
return 0;
}
Вы можете использовать попробовать... поймать.
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
Если вам нужно разорвать несколько петель одновременно, это все равно часто является исключением.
Выход из цикла for немного странен для меня, так как семантика цикла for обычно указывает на то, что он будет выполняться определенное количество раз. Тем не менее, это не плохо во всех случаях; если вы ищете что-то в коллекции и хотите сломать ее после того, как найдете ее, это полезно. Однако вырваться из вложенных циклов в C++ невозможно; это на других языках с помощью помеченного разрыва. Вы можете использовать ярлык и goto, но это может вызвать у вас изжогу ночью..? Похоже, лучший вариант, хотя.