从该循环中调用的函数中断开循环

时间:2016-02-15 08:04:26

标签: c for-loop break

我目前正在尝试找出一种方法来摆脱该循环中调用的函数内的DateTime循环。我知道有可能让函数返回一个值,然后检查一个特定的值然后中断,但我想直接在函数内执行它。 这是因为我使用内部库来处理特定硬件,要求我的函数的函数签名如下所示:

for

我知道不使用返回值是非常糟糕的做法,但是有很多情况迫使我这样做,所以请耐心等待。

请考虑以下示例:

void foo (int passV, int aVal, long bVal)

现在这不编译。相反,我得到一个编译错误如下:

  

prog.c:在函数' foo':   prog.c:6:2:错误:break语句没有   在循环或开关中断;

我知道这意味着什么(编译器说#include <stdio.h> void foo (int a) { printf("a: %d", a); break; } int main(void) { for (int i = 0; i <= 100; i++) { foo(i); } return 0; } 中的中断不在foo()循环中)

现在,我可以从关于break语句的标准中找到的是:

  

break语句使控件传递给后面的语句   最里面封闭while,do,for或switch语句。该   语法只是简单;

考虑到我的函数是从for循环中调用的,为什么break语句没有突破所说的for循环?此外,是否可以在没有函数返回的情况下实现这样的事情?

14 个答案:

答案 0 :(得分:44)

您无法以这种方式使用break;,它必须出现在for循环体内。

有几种方法可以做到这一点,但建议不要这样做:

  • 您可以使用exit()功能退出程序。由于循环是从main()开始的,并且你之后没有做任何事情,所以可以通过这种方式实现你想要的东西,但它是一个特例。

  • 您可以在函数中设置全局变量,并在函数调用后在for循环中测试该变量。通常不建议使用全局变量。

  • 你可以使用setjmp()longjmp(),但这就像试图用锤子击打苍蝇,你可能会破坏其他东西并完全错过苍蝇。我不推荐这种方法。此外,它需要jmpbuf,您必须将其作为全局变量传递给函数或访问。

可接受的替代方法是将status变量的地址作为额外参数传递:函数可以设置它以指示需要从循环中断开。

但到目前为止,C中最好的方法是返回一个值来测试延续,它是最可读的。

根据您的解释,您没有foo()的源代码,但可以检测您可以通过foo()直接或间接调用的函数中的某些条件:{{1} }}将从longjmp()的内部深处,可能在调用堆栈的多个级别内跳到foo()位置,绕过所有中间调用的常规函数​​退出代码。如果这正是您需要做的以避免崩溃,setjmp() / setjmp()是一个解决方案,但它可能会导致其他问题,如资源泄漏,缺少初始化,状态不一致等未定义行为的来源。

请注意,由于您使用longjmp()运算符,因此for循环将迭代101次。惯用<=循环使用for来精确迭代显示为上(排除)边界的次数。

答案 1 :(得分:24)

breakgoto一样,只能在同一个函数中本地跳转,但如果您必须这样做,则可以使用setjmplongjmp

#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跳跃&#34;通过&#34; setjmp调用和longjmp调用之间的所有函数激活,如果这些函数中的任何一个希望能够在执行当前位置之后执行其他工作,那么这项工作将无法完成。 / LI>
  • 如果调用setjmp的函数激活已终止,则行为未定义。任何事情都可能发生。
  • 如果尚未调用setjmp,则未设置jump_target,且行为未定义。
  • 调用setjmp的函数中的局部变量在某些条件下可能具有未定义的值。
  • 一个字:线程。
  • 可能无法保留其他内容,例如浮点状态标记,以及setjmp调用的位置存在限制。

如果您对机器指令和CPU寄存器的非本地跳转有很好的理解,那么大多数都会自然地遵循,但除非你有,已经阅读了C标准不保证,我会建议谨慎。

答案 2 :(得分:9)

(注意:自我最初撰写本文以来,该问题已被编辑)

由于编译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);

答案 3 :(得分:8)

break是在编译期间解析的语句。因此,编译器必须在同一函数中找到适当的for / while循环。请注意,无法保证不能从其他地方调用该函数。

答案 4 :(得分:6)

如果您不能使用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;
}

答案 5 :(得分:4)

我认为这与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.

答案 6 :(得分:4)

这是另一个可能或可能不可行的想法:保持变量可以将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,则可以将其重置为0。

所以不是很好,但是还没有提到一个想法。

答案 7 :(得分:4)

在这种情况下,考虑使用while()循环和几个用&amp;&amp;链接的条件语句。而不是for循环。尽管可以使用setjmp和longjmp等函数来改变正常的控制流,但它几乎被认为是不好的做法。你不应该在这个网站上搜索太多以找出原因。 (简而言之,因为它具有创建复杂控制流的能力,而这种控制流并不适用于调试或人工理解)

还要考虑做这样的事情:

int foo (int a) {
    if(a == someCondition) return 0;
    else {
        printf("a: %d", a);
        return 1;
    }
}

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

在这种情况下,循环取决于从'foo&#39;返回的真值,如果条件内有条件,则会打破循环#39; foo&#39;不满足。

编辑:我并没有明确反对使用goto,setjmp,longjmp等。但我认为在这种情况下,有一个更简单,更简洁的解决方案,而无需采取这些措施!

答案 8 :(得分:4)

如果你不能处理返回值,你至少可以在函数中添加一个参数: 我可以想象这样的解决方案:

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;
  }
}

这是我的第一篇文章,所以,请原谅我,如果我的格式搞砸了:)

答案 9 :(得分:3)

根据您更新的问题清楚地列出限制,我建议您在函数内部移动整个循环,然后调用第二个函数,该函数在该函数内具有返回值,例如

#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;
   }
}

这可以避免任何极端和脆弱的体操绕过限制。

答案 10 :(得分:3)

只需设置一个全局变量并在循环中检查:

#include <stdio.h>

int leave = 0;

void foo (int a) {
    printf("a: %d", a);
    leave = 1;
}

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

答案 11 :(得分:2)

考虑在for循环中手动内联函数。如果在多个循环中调用此函数,请将其定义为宏:

#define f()\
printf("a: %d", a);\
break;

答案 12 :(得分:2)

您可以在循环内的函数中抛出一个错误,并在循环外捕获该错误。

#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;
}

答案 13 :(得分:1)

这个问题已经回答了,但是我认为值得研究退出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";

这将输出当前数字,并将其递增1,直到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是否确实提高了执行力。通常,使用breakreturn条件更快,因为编译器很难理解包含goto的代码。