Выход из цикла из функции, вызываемой в этом цикле

Контроль доступа применяется к именам. Сравните этот пример со стандартом:

class A {
  class B { };
public:
  typedef B BB;
};

void f() {
  A::BB x; // OK, typedef name A::BB is public
  A::B y; // access error, A::B is private
}
29
задан rohinMVP 16 February 2016 в 08:40
поделиться

12 ответов

break, как и goto, могут перемещаться только локально в пределах одной и той же функции, но если вам абсолютно необходимо, вы можете использовать setjmp и longjmp:

#include <stdio.h>
#include <setjmp.h>

jmp_buf jump_target;

void foo(void)
{
    printf("Inside foo!\n");
    longjmp(jump_target, 1);
    printf("Still inside foo!\n");
}

int main(void) {
    if (setjmp(jump_target) == 0)
        foo();
    else
        printf("Jumped out!\n");
    return 0;
}

Вызов на longjmp вызовет переход к вызову setjmp. Возвращаемое значение из setjmp показывает, возвращается ли оно после установки цели прыжка или возвращается с прыжка.

Вывод:

Inside foo!
Jumped out!

Нелокальные скачки безопасны при правильном использовании, но есть ряд вещей, о которых следует тщательно подумать:

  • Поскольку longjmp прыгает «через «все активации функций между вызовом setjmp и вызовом longjmp, если какая-либо из этих функций ожидает выполнения дополнительной работы после выполнения текущего места, эта работа просто не будет выполнена.
  • Если активация функции, вызвавшая setjmp, завершилась, поведение не определено. Все может случиться.
  • Если setjmp еще не был вызван, то jump_target не установлено, и поведение не определено.
  • Локальные переменные в функции, вызвавшей setjmp, могут при определенных условиях иметь неопределенные значения.
  • Одно слово: темы.
  • Другие вещи, такие как флаги состояния с плавающей запятой, могут не сохраняться, и есть ограничения на то, куда вы можете поместить вызов setjmp.

Большинство из них следуют естественным образом, если вы хорошо понимаете, что делает нелокальный переход на уровне машинных инструкций и регистров ЦП, но, если у вас нет этого, и прочитали, что Стандарт С делает и не гарантирует, я бы посоветовал немного предостеречь.

24
ответ дан Thomas Padron-McCarthy 16 February 2016 в 08:40
поделиться

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

#include <stdio.h>

void foo (int a) {
    printf("a: %d", a);
    if (a == 50)
    {
       throw a;
    }
}

int main(void) {
    try {
        for (int i = 0; i <= 100; i++) {
            foo(i);
        }
    catch(int e) {
    }
    return 0;
}
2
ответ дан Russell Hankins 16 February 2016 в 08:40
поделиться

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

  • Использование условия цикла
  • Использование условия break
  • Использование условия return
  • Использование исключения
  • Использование goto

Далее я опишу варианты использования этих опций с использованием c ++ 14. Однако вы можете делать все это в более ранних версиях c ++ (за исключением, возможно, исключений). Короче говоря, я опущу функции include и main. Пожалуйста, прокомментируйте, если вы считаете, что какая-то часть нуждается в большей ясности.

1. Использование условия цикла

Стандартным способом выхода из цикла является условие цикла. Условие цикла записывается в средней части оператора for или в скобках оператора while:

for(something; LOOP CONDITION; something) {
    ... 
}
while (LOOP CONDITION)
    ... 
}
do {
    ... 
} while (LOOP CONDITION);

Условие цикла решает, следует ли вводить цикл, и если цикл должен быть повторен. Во всех вышеупомянутых случаях условие должно быть true, чтобы цикл повторялся.

Например, если мы хотим вывести число от 0 до 2, мы могли бы написать код, используя цикл и условие цикла:

for (auto i = 0; i <= 2; ++i)
    std::cout << i << '\n';
std::cout << "done";

Здесь условие - i <= 2. Пока это условие оценивается как true, цикл продолжает работать.

Альтернативная реализация заключалась бы в том, чтобы вместо этого поместить условие в переменную:

auto condition = false;

for (auto i = 0; !condition; ++i) {
    std::cout << i << '\n';
    condition = i > 2;
}
std::cout << "done";

Проверяя вывод для обеих версий, мы получаем желаемый результат:

0
1
2
done

Как бы вы использовали условие цикла в реальном приложении?

Обе версии широко используются в проектах c ++. Важно отметить, что первая версия более компактна и поэтому легче для понимания. Но вторая версия обычно используется, если условие более сложное или требуется несколько шагов для оценки.

Например:

auto condition = false;
for (auto i = 0; !condition; ++i)
    if (is_prime(i))
        if (is_large_enough(i)) {
            key = calculate_cryptographic_key(i, data);
            if (is_good_cryptographic_key(key))
                condition = true;
        }

2. Использование условия break

Другой простой способ выйти из цикла - использовать ключевое слово break. Если он используется внутри цикла, выполнение остановится и продолжится после тела цикла:

for (auto i = 0; true; ++i) {
    if (i == 3)
        break;
    std::cout << i << '\n';
}
std::cout << "done";

Это выведет текущее число и увеличит его на единицу, пока i не достигнет значения 3. Здесь утверждение if является нашим условием break. Если условие true, цикл прерывается (обратите внимание на !), и выполнение продолжается со следующей строки, печатающей done.

Выполняя тест, мы действительно получаем ожидаемый результат:

0
1
2
done

Важно, что это остановит только самый внутренний цикл в коде. Поэтому, если вы используете несколько циклов, это может привести к нежелательному поведению:

for (auto j = 0; true; ++j)
    for (auto i = 0; true; ++i) {
        if (i == 3)
            break;
        std::cout << i << '\n';
    }
std::cout << "done";

С помощью этого кода мы хотели получить тот же результат, что и в примере выше, но вместо этого мы получаем бесконечный цикл, потому что break останавливает цикл только над i, а не цикл j!

Выполнение теста:

0
1
2
0
1
2
...

Как бы вы использовали break ] условие в реальном приложении?

Обычно break используется только для пропуска частей внутреннего цикла или для добавления дополнительного выхода из цикла.

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

auto is_prime = true;
for (auto i = 0; i < p; ++i) {
    if (p%i == 0) { //p is dividable by i!
        is_prime = false;
        break; //we already know that p is not prime, therefore we do not need to test more cases!
    }

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

auto j = size_t(0);
for (auto i = size_t(0); i < data.size(); ++i)
    if (data[i] == "Hello") { //we found "Hello"!
        j = i;
        break; //we already found the string, no need to search any further!
    }

3. Использование условия return

Ключевое слово return выходит из текущей области и возвращается к вызывающей функции. Таким образом, его можно использовать для выхода из цикла и, кроме того, для возврата номера вызывающей стороне. Распространенным случаем является использование return для выхода из цикла (и его функции) и возврата результата.

Например, мы можем переписать функцию is_prime сверху:

auto inline is_prime(int p) {
    for (auto i = 0; i < p; ++i)
        if (p%i == 0) //p is dividable by i!
            return false; //we already know that p is not prime, and can skip the rest of the cases and return the result
    return true; //we didn't find any divisor before, thus p must be prime!
}

Ключевое слово return также можно использовать для выхода из нескольких циклов:

auto inline data_has_match(std::vector<std::string> a, std::vector<std::string> b) {
    for (auto i = size_t(0); i < a.size(); ++i)
        for (auto j = size_t(0); j < a.size(); ++j)
            if (a[i] == b[j])
                return true; //we found a match! nothing to do here
    return false; //no match was found
}

Как бы вы использовали условие return в реальном приложении?

Внутри небольших функций return часто используется для выхода из циклов и напрямую вернуть результаты. Кроме того, внутри больших функций return помогает сохранять код понятным и читабельным:

for (auto i = 0; i < data.size(); ++i) {
    //do some calculations on the data using only i and put them inside result
    if (is_match(result,test))
        return result;
    for (auto j = 0; j < i; ++j) {
        //do some calculations on the data using i and j and put them inside result
        if (is_match(result,test))
            return result;
    }
}
return 0; //we need to return something in the case that no match was found

Что гораздо легче понять, чем:

auto break_i_loop = false;
auto return_value = 0;
for (auto i = 0; !break_i_loop; ++i) {
    //do some calculations on the data using only i and put them inside result
    if (is_match(result,test)) { //a match was found, save the result and break the loop!
        return_value = result;
        break;
    }
    for (auto j = 0; j < i; ++j) {
        //do some calculations on the data using i and j and put them inside result
        if (is_match(result,test)) { //a match was found, save the result, break the loop, and make sure that we break the outer loop too!
            return_value = result;
            break_i_loop = true;
            break;
        }
    }
    if (!break_i_loop) //if we didn't find a match, but reached the end of the data, we need to break the outer loop
        break_i_loop = i >= data.size();
}
return return_value; //return the result

4. Использование исключений

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

Как бы вы использовали исключение в реальном приложении?

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

auto inline inverse_data(std::vector<int>& data) {
    for (auto i = size_t(0); i < data.size(); ++i)
        if (data[i] == 0)
            throw std::string("Division by zero on element ") + std::to_string(i) + "!";
        else
            data[i] = 1 / data[i];
}

Мы можем обработать это исключение внутри вызывающей функции:

while (true)
    try {
        auto data = get_user_input();
        inverse = inverse_data(data);
        break;
    }
    catch (...) {
        std::cout << "Please do not put zeros into the data!";
    }

Если data содержит ноль, то inverse_data выдает исключение, break никогда не выполняется, и пользователь должен снова вводить данные.

Есть еще больше опций для такого рода обработки ошибок, с дополнительными типами ошибок, ..., но это тема для другого дня.

** Что вы никогда не должны делать! **

Как упоминалось ранее, исключения могут привести к значительным накладным расходам во время выполнения. Поэтому их следует использовать только в действительно исключительных случаях. Хотя можно написать следующую функцию, не надо!

auto inline next_prime(int start) {
    auto p = start;
    try {
        for (auto i = start; true; ++i)
            if (is_prime(i)) {
                p = i;
                throw;
            }
   }
   catch (...) {}
   return p;
 }

5. Использование goto

Ключевое слово goto ненавидимо большинством программистов, потому что оно затрудняет чтение кода и может иметь непредвиденные побочные эффекты. Однако его можно использовать для выхода из (нескольких) циклов:

for (auto j = 0; true; ++j)
    for (auto i = 0; true; ++i) {
        if (i == 3)
            goto endloop;
        std::cout << i << '\n';
    }
endloop:
std::cout << "done";

Этот цикл закончится (не как цикл в части 2) и выдает:

0
1
2
done

Как бы вы использовали goto в реальных приложениях?

В 99,9% случаев нет необходимости использовать ключевое слово goto. Единственным исключением являются встроенные системы, такие как Arduino, или код с очень высокой производительностью. Если вы работаете с одним из этих двух, вы можете использовать goto для создания более быстрого или более эффективного кода. Тем не менее, для обычного программиста, недостатки намного больше, чем выгоды от использования goto.

Даже если вы считаете, что ваш случай составляет один из 0,1%, вам нужно проверить, действительно ли goto улучшает ваше исполнение. Чаще всего использование условия break или return происходит быстрее, потому что компилятору сложно понять код, содержащий goto.

1
ответ дан jan.sende 16 February 2016 в 08:40
поделиться

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

void main (void)
{
  int a = 0;

  for (; 1 != a;)
  {
    foo(x, &a);
  } 
}

void foo( int x, int * a)
{
  if (succeeded)
  {
    /* set the break condition*/
    *a = 1;
  }
  else
  {
    *a = 0;
  }
}

Это мой первый пост, поэтому, пожалуйста, простите меня, если мое форматирование испорчено :)

4
ответ дан Alexander Werner 16 February 2016 в 08:40
поделиться

Подумайте о включении вашей функции вручную в цикл for. Если эта функция вызывается в нескольких циклах, определите ее как макрос:

#define f()\
printf("a: %d", a);\
break;
2
ответ дан Dmitry Grigoryev 16 February 2016 в 08:40
поделиться

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

.
#include <stdbool.h>

bool foo (int x)
{
    return (x==14);
}

void loopFoo(int passV, int aVal, long bVal)
{
   for (int i = 0; i <= 100; ++i)
   {
       if (foo(x))
           break;
   }
}

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

3
ответ дан Jack Aidley 16 February 2016 в 08:40
поделиться

(Примечание: вопрос был отредактирован с тех пор, как я его написал)

Из-за способа компиляции C он должен знать, куда переходить при вызове функции. Поскольку вы можете вызывать его из любого места, или даже где-то разрыв не имеет смысла, вы не можете иметь оператор break; в своей функции и заставить его работать так.

В других ответах предлагались ужасные решения, такие как установка глобальной переменной с использованием #define или longjumping (!) Из функции. Это очень плохие решения. Вместо этого вы должны использовать решение, которое вы ошибочно отклонили в своем первом абзаце, и вернуть значение из вашей функции, которое указывает состояние, в котором вы хотите вызвать break в этом случае, и сделать что-то вроде этого:

#include <stdbool.h>

bool checkAndDisplay(int n)
{
    printf("%d\n", n);
    return (n == 14);
}

int main(void) {
    for (int i = 0; i <= 100; i++) {
        if (checkAndDisplay(i))
            break;
    }
    return 0;
}

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

Вы упомянули, скрытые в комментарии, что вы должны использовать возврат void, это не проблема, просто передайте параметр break в качестве указателя:

#include <stdbool.h>

void checkAndDisplay(int n, bool* wantBreak)
{
    printf("%d\n", n);
    if (n == 14)
        wantBreak = true;
}

int main(void) {
    bool wantBreak = false;
    for (int i = 0; i <= 100; i++) {
        checkAndDisplay(i, &wantBreak);
        if (wantBreak)
            break;
    }
    return 0;
}

Поскольку ваши параметры имеют фиксированный тип Я предлагаю вам использовать приведение для передачи указателя на один из параметров, например foo(a, b, (long)&out);

9
ответ дан Jack Aidley 16 February 2016 в 08:40
поделиться

Это еще одна идея, которая может или не может быть осуществима: держите переменную, которая может превратить foo в неоперативный:

int broken = 0;

void foo (int a) {
    if (broken) return;

    printf("a: %d", a);
    broken = 1; // "break"
}

int main(void) {
    for (int i = 0; i <= 100; i++) {
        foo(i);
    }
    return 0;
}

Это функционально то же самое, за исключением некоторой потери тактов (функция будет вызываться, но выполнять только оператор if), и нет необходимости изменять цикл. Это не потокобезопасно и работает только в первый раз (но foo может сбросить переменную broken в 0, если вызывается с a, равным 0, если необходимо).

Так что не велика, но идея, которая еще не была упомянута.

4
ответ дан RemcoGerlich 16 February 2016 в 08:40
поделиться

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

8
ответ дан Zbynek Vyskovsky - kvr000 16 February 2016 в 08:40
поделиться

Я считаю, что это связано с тем, как оператор break переводится в машинный код. Оператор break будет переведен как безусловная ветвь на метку сразу после цикла или переключателя.

mov ECX,5
label1:
  jmp <to next instruction address>  ;break
loop label1
<next instruction>

Хотя вызов foo() изнутри цикла приведет к чему-то вроде

mov ECX,5
label1:
  call <foo address>
loop label1
<next instruction>

и по адресу foo

call <printf address>
jmp <to where?> ;break cannot translate to any address.
4
ответ дан wizzwizz4 16 February 2016 в 08:40
поделиться

Если вы не можете использовать инструкцию break, вы можете определить локальную переменную в вашем модуле и добавить условие второго запуска в цикл for. Например, как следующий код:

#include <stdio.h>
#include <stdbool.h>

static bool continueLoop = true;

void foo (int a)
{
    bool doBreak = true;

    printf("a: %d",a);

    if(doBreak == true){
        continueLoop = false;
    }
    else {
        continueLoop = true;
    }
}
int main(void) {
    continueLoop = true;   // Has to be true before entering the loop
    for (int i = 0; (i <= 100) && continueLoop; i++)
    {
        foo(i);
    }
    return 0;
}

Обратите внимание, что в этом примере это не совсем break -инструкция, но цикл for не будет выполнять другую итерацию. Если вы хотите сделать break, вы должны вставить if -условие с переменной continueLoop, что приводит к break:

int main(void) {
    continueLoop = true;   // Has to be true before entering the loop
    for (int i = 0; i <= 100; i++)
    {
        foo(i);
        if(!continueLoop){
            break;
        }
    }
    return 0;
}
6
ответ дан Frodo 16 February 2016 в 08:40
поделиться

Вы не можете использовать break; таким образом, оно должно появляться внутри тела цикла for.

Есть несколько способов сделать это, но ни один не рекомендуется:

  • Вы можете выйти из программы с помощью функции exit(). Поскольку цикл запускается из main(), и вы ничего не делаете после него, можно добиться того, чего вы хотите, таким образом, но это как особый случай.

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

  • вы можете использовать setjmp() и longjmp(), но это все равно, что пытаться раздавить муху молотком, вы можете сломать другие вещи и вообще пропустить муху. Я бы не рекомендовал такой подход. Кроме того, требуется jmpbuf, что вам нужно будет передать в функцию или получить доступ в качестве глобальной переменной.

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

Но, безусловно, лучший подход в C - возвращать значение для проверки продолжения , оно наиболее читаемо.

Из ваших объяснений у вас нет исходного кода для foo(), но вы можете обнаружить некоторые условия в функции, которую вы можете изменить, вызывая прямо или косвенно с помощью foo(): longjmp() выпрыгнет из своего местоположения, глубоко внутри foo(), возможно, много уровней вниз по стеку вызовов, до местоположения setjmp(), минуя обычный код завершения функции для всех промежуточных вызовов. Если это именно то, что вам нужно сделать, чтобы избежать сбоя, setjmp() / longjmp() является решением, но оно может вызвать другие проблемы, такие как утечка ресурсов, отсутствующая инициализация, несогласованное состояние и другие источники неопределенного поведения.

Обратите внимание, что ваш цикл for будет повторяться 101 раз, потому что вы используете оператор <=. Идиоматический цикл for использует for (int i = 0; i < 100; i++) для итерации в точности столько раз, сколько отображается в качестве верхней (исключенной) границы.

44
ответ дан chqrlie 16 February 2016 в 08:40
поделиться
Другие вопросы по тегам:

Похожие вопросы: