salir - como romper un ciclo while en arduino
Salir de un bucle desde dentro de una funciĆ³n llamada en ese bucle (14)
(Nota: la pregunta ha sido editada desde que originalmente escribí esto)
Debido a la forma en que se compila C, debe saber a dónde ir cuando se llama a la función. Como puedes llamar desde cualquier lugar, o incluso en algún lugar, un descanso no tiene sentido, no puedes tener un break;
declaración en tu función y haz que funcione así.
Otras respuestas han sugerido soluciones terribles, como establecer una variable global, utilizando #define
o longjumping (!) Fuera de la función. Estas son soluciones extremadamente pobres. En su lugar, debe usar la solución que rechazó erróneamente en el párrafo inicial y devolver un valor de su función que indique el estado en el que desea desencadenar un break
en este caso y hacer algo como esto:
#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;
}
Tratar de encontrar formas oscuras de lograr cosas como esta en lugar de utilizar la forma correcta de lograr el mismo resultado final es una manera infalible de generar código de calidad terrible que es una pesadilla para mantener y depurar.
Menciona, oculto en un comentario, que debe usar un retorno nulo, esto no es un problema, simplemente pase el parámetro break como un puntero:
#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;
}
Como sus parámetros son fijos, le sugiero que use un molde para pasar el puntero a uno de los parámetros, por ejemplo, foo(a, b, (long)&out);
Actualmente estoy tratando de encontrar una forma de salir de un bucle for
desde dentro de una función llamada en ese bucle. Soy consciente de la posibilidad de que la función devuelva un valor y luego se compara con un valor particular y luego se rompe, pero me gustaría hacerlo directamente desde la función. Esto se debe a que estoy usando una biblioteca interna para una pieza específica de hardware que exige que la firma de la función de mi función se vea así:
void foo (int passV, int aVal, long bVal)
Soy consciente de que no usar un valor de retorno es una práctica muy mala, pero por desgracia las circunstancias me obligan a hacerlo, así que por favor tengan paciencia conmigo.
Considera el siguiente ejemplo:
#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;
}
Ahora esto no compila. En cambio, obtengo un error de compilación de la siguiente manera:
prog.c: en la función ''foo'': prog.c: 6: 2: error: declaración de corte no dentro del ciclo o interrupción de conmutación;
Sé lo que esto significa (el compilador dice que el corte en foo()
no está dentro de un bucle for
)
Ahora, lo que pude encontrar del estándar con respecto a la declaración de interrupción es este:
La declaración de interrupción hace que el control pase a la instrucción que sigue a la instrucción de encerrar while, do, for o switch. La sintaxis es simplemente break;
Teniendo en cuenta que mi función se llama desde dentro de un ciclo for, ¿por qué la instrucción break no sale de dicho ciclo for? Además, ¿es posible darse cuenta de algo como esto sin tener que devolver la función primero?
Considere incluir su función manualmente en el ciclo for
. Si se llama a esta función en múltiples bucles, defínala como una macro:
#define f()/
printf("a: %d", a);/
break;
Creo que está relacionado con la forma en que una sentencia break
se traduce en código máquina. La declaración de break
se traducirá como una rama incondicional a la etiqueta que sigue inmediatamente al bucle o interruptor.
mov ECX,5
label1:
jmp <to next instruction address> ;break
loop label1
<next instruction>
Mientras que la llamada a foo()
desde el interior del bucle dará como resultado algo así como
mov ECX,5
label1:
call <foo address>
loop label1
<next instruction>
y en la dirección de foo
call <printf address>
jmp <to where?> ;break cannot translate to any address.
Después de su pregunta actualizada que establece claramente las limitaciones, le sugiero que mueva todo el ciclo dentro de su función y luego llame a una segunda función con un valor de retorno dentro de esa función, por ej.
#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;
}
}
Esto evita cualquier gimnasia extrema y frágil para evitar la limitación.
En un caso como este, considere usar un ciclo while () con varios enunciados condicionales encadenados con && en lugar de un ciclo for. Aunque puede alterar el flujo de control normal usando funciones como setjmp y longjmp, se considera una mala práctica en todas partes. No debería tener que buscar demasiado en este sitio para descubrir por qué. (En resumen, es debido a su capacidad para crear un flujo de control intrincado que no se presta para la depuración o la comprensión humana)
También considera hacer algo como esto:
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;
}
En este caso, el ciclo depende de que se devuelva un valor verdadero de ''foo'', que romperá el ciclo si no se cumple la condición dentro de ''foo''.
Editar: no estoy explícitamente en contra del uso de goto, setjmp, longjmp, etc. Pero creo que en este caso hay una solución mucho más simple y concisa disponible sin recurrir a estas medidas.
Esta es otra idea que puede o no ser factible: mantener una variable que pueda convertir a foo en una no-operación:
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;
}
Esto es funcionalmente el mismo excepto por alguna pérdida de ciclos de reloj (se llamará a la función, pero solo realizará la instrucción if
), y no hay necesidad de cambiar el ciclo. No es seguro para los hilos y solo funciona la primera vez (pero foo
podría restablecer la variable broken
a 0 si se llama con a
igual a 0, si es necesario).
Así que no es genial, pero una idea que no se mencionó aún.
Esta pregunta ya ha sido respondida, pero creo que vale la pena profundizar en todas las opciones posibles para salir de un bucle en c ++. Básicamente hay cinco posibilidades:
- Usando una condición de bucle
- Usando una condición de
break
- Usando una condición de
return
- Usando excepciones
- Usando
goto
A continuación, describiré casos de uso para estas opciones usando c ++ 14. Sin embargo, puede hacer todo esto en versiones anteriores de c ++ (excepto excepciones). Para mantenerlo corto, omitiré los includes y la función principal. Comente, si cree que alguna parte necesita más claridad.
1. Usando una condición de bucle
La forma estándar de salir de un bucle es una condición de bucle. La condición de bucle está escrita en la parte central de una instrucción for
, o entre los paréntesis de una instrucción while
:
for(something; LOOP CONDITION; something) {
...
}
while (LOOP CONDITION)
...
}
do {
...
} while (LOOP CONDITION);
La condición de bucle decide si se debe ingresar el bucle, y si el bucle se debe repetir. En todos los casos anteriores, la condición debe ser true
, para que el ciclo se repita.
Como ejemplo, si queremos generar el número de 0 a 2, podríamos escribir el código usando un bucle y una condición de bucle:
for (auto i = 0; i <= 2; ++i)
std::cout << i << ''/n'';
std::cout << "done";
Aquí la condición es i <= 2
. Mientras esta condición se evalúe como true
, el ciclo sigue en funcionamiento.
Una implementación alternativa sería poner la condición en una variable en su lugar:
auto condition = false;
for (auto i = 0; !condition; ++i) {
std::cout << i << ''/n'';
condition = i > 2;
}
std::cout << "done";
Verificando el resultado para ambas versiones, obtenemos el resultado deseado:
0
1
2
done
¿Cómo usarías una condición de bucle en una aplicación del mundo real?
Ambas versiones son ampliamente utilizadas dentro de proyectos de C ++. Es importante tener en cuenta que la primera versión es más compacta y, por lo tanto, más fácil de entender. Pero la segunda versión generalmente se usa si la condición es más compleja o necesita varios pasos para ser evaluada.
Por ejemplo:
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. Usando una condición de break
Otra forma simple de salir de un bucle es usar la palabra clave break
. Si se usa dentro del ciclo, la ejecución se detendrá y continuará después del cuerpo del ciclo:
for (auto i = 0; true; ++i) {
if (i == 3)
break;
std::cout << i << ''/n'';
}
std::cout << "done";
Esto generará el número actual, y lo incrementará en uno, hasta que llegue a un valor de 3. Aquí la declaración if
es nuestra condición de break
. Si la condición es true
, el bucle está roto (¡tenga en cuenta !
) Y la ejecución continúa con la siguiente línea, la impresión done
.
Al hacer la prueba, de hecho obtenemos el resultado esperado:
0
1
2
done
Es importante, que esto solo detendrá el ciclo más interno en el código. Por lo tanto, si usa múltiples bucles, puede conducir a un comportamiento no deseado:
for (auto j = 0; true; ++j)
for (auto i = 0; true; ++i) {
if (i == 3)
break;
std::cout << i << ''/n'';
}
std::cout << "done";
Con este código queríamos obtener el mismo resultado que en el ejemplo anterior, pero en cambio obtenemos un bucle infinito, porque el break
solo detiene el ciclo sobre i
, ¡y no el que está sobre j
!
Haciendo la prueba:
0
1
2
0
1
2
...
¿Cómo usarías una condición de break
en una aplicación real?
Por lo general, la break
solo se usa para omitir partes de un bucle interno o para agregar una salida de bucle adicional.
Por ejemplo, en una función de prueba para números primos, la usaría para omitir el resto de la ejecución, tan pronto como encuentre un caso en el que el número actual no sea primo:
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!
}
O bien, si está buscando un vector de cadenas, generalmente coloca el tamaño máximo de los datos en la cabeza del bucle, y usa una condición adicional para salir del bucle si realmente encontró los datos que está buscando.
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. Usando una condición de return
La palabra clave de return
sale del alcance actual y vuelve a la función de llamada. Por lo tanto, se puede usar para salir de bucles y, además, devolverle un número a la persona que llama. Un caso común es utilizar return
para salir de un bucle (y su función) y devolver un resultado.
Por ejemplo, podemos reescribir la función is_prime
desde arriba:
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!
}
La palabra clave return
también se puede usar para salir de múltiples bucles:
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
}
¿Cómo usarías una condición de return
en una aplicación real?
Dentro de funciones más pequeñas, el return
se usa a menudo para salir de bucles y devolver resultados directamente. Además, dentro de funciones más grandes, el return
ayuda a mantener el código claro y legible:
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
Lo cual es mucho más fácil de entender que:
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. Uso de excepciones
Las excepciones son una forma de marcar eventos excepcionales en su código. Por ejemplo, si desea leer datos de un archivo, pero por alguna razón ¡el archivo no existe! Las excepciones se pueden usar para salir de los bucles, sin embargo, el compilador generalmente genera una gran cantidad de código repetitivo para continuar el programa de manera segura si se maneja la excepción. Por lo tanto, las excepciones no se deben usar para devolver valores, porque es muy ineficiente.
¿Cómo usarías una excepción en una aplicación del mundo real?
Las excepciones se usan para manejar casos verdaderamente excepcionales. Por ejemplo, si queremos calcular el inverso de nuestros datos, puede suceder que intentemos dividir por cero. Sin embargo, esto no es útil en nuestro cálculo, por lo tanto, escribimos:
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];
}
Podemos manejar esta excepción dentro de la función de llamada:
while (true)
try {
auto data = get_user_input();
inverse = inverse_data(data);
break;
}
catch (...) {
std::cout << "Please do not put zeros into the data!";
}
Si los data
contienen cero, inverse_data
arrojará una excepción, el inverse_data
nunca se ejecutará y el usuario tendrá que inverse_data
a ingresar los datos.
Hay incluso más opciones avanzadas para este tipo de manejo de errores, con tipos de error adicionales, ..., pero este es un tema para otro día.
** ¡Lo que nunca deberías hacer! **
Como se mencionó anteriormente, las excepciones pueden producir una sobrecarga de tiempo de ejecución significativa. Por lo tanto, solo deben usarse en casos verdaderamente excepcionales. Aunque es posible escribir la siguiente función, ¡por favor no!
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. Usando goto
La mayoría de los programadores odian la palabra clave goto
porque dificulta la lectura del código y puede tener efectos secundarios no deseados. Sin embargo, se puede usar para salir (múltiples) bucles:
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";
Este bucle terminará (no como el bucle en la parte 2), y la salida:
0
1
2
done
¿Cómo goto
un goto
en una aplicación del mundo real?
En el 99.9% de los casos, no es necesario usar la palabra clave goto
. Las únicas excepciones son los sistemas integrados, como un Arduino, o un código de muy alto rendimiento. Si está trabajando con uno de estos dos, es posible que desee usar goto
para producir un código más rápido o más eficiente. Sin embargo, para el programador de todos los días, las desventajas son mucho mayores que las ganancias de usar goto
.
Incluso si piensa que su caso es uno del 0.1%, necesita verificar si el goto
realidad mejora su ejecución. La mayoría de las veces, usar una condición de break
o return
es más rápido, porque el compilador tiene un código de comprensión de tiempo más difícil que contiene goto
.
No puedes usar break;
de esta manera, debe aparecer dentro del cuerpo del bucle for
.
Hay varias formas de hacerlo, pero ninguna es recomendable:
puede salir del programa con la función
exit()
. Como el ciclo se ejecuta desdemain()
y no hace nada después de él, es posible lograr lo que desea de esta manera, pero como un caso especial.Puede establecer una variable global en la función y probarla en el ciclo
for
después de la llamada a la función. Usar variables globales generalmente no es una práctica recomendada.puedes usar
setjmp()
ylongjmp()
, pero es como tratar de aplastar una mosca con un martillo, puedes romper otras cosas y perder la mosca por completo. No recomendaría este enfoque. Además, requiere unjmpbuf
que tendrá que pasar a la función o acceder como una variable global.
Una alternativa aceptable es pasar la dirección de una variable de status
como un argumento adicional: la función puede configurarlo para indicar la necesidad de romper el ciclo.
Pero con mucho, el mejor enfoque en C es devolver un valor para probar la continuación , es el más legible.
Según sus explicaciones, no tiene el código fuente para foo()
pero puede detectar algunas condiciones en una función que puede modificar llamada directa o indirectamente por foo()
: longjmp()
saltará desde su ubicación, en el interior de las internas de foo()
, posiblemente muchos niveles en la pila de llamadas, a la ubicación setjmp()
, omitiendo el código de salida de la función normal para todas las llamadas intermedias. Si eso es precisamente lo que debe hacer para evitar un bloqueo, setjmp()
/ longjmp()
es una solución, pero puede causar otros problemas, como la pérdida de recursos, falta de inicialización, estado incoherente y otras fuentes de comportamiento indefinido.
Tenga en cuenta que su ciclo for
iterará 101
veces porque usa el operador <=
. El bucle idiomático se usa for (int i = 0; i < 100; i++)
para repetir exactamente el número de veces que aparece como el límite superior (excluido).
Puede arrojar un error en su función dentro del ciclo y detectar ese error fuera del ciclo.
#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;
}
Si no puede manejar los valores devueltos, al menos puede agregar un parámetro a la función: puedo imaginar una solución como esa:
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;
}
}
Es mi primera publicación, así que, por favor, perdónenme si mi formateo está estropeado :)
Si no puede usar la instrucción break
, puede definir una variable local en su módulo y agregar una segunda condición de ejecución al bucle for
. Por ejemplo, como el siguiente código:
#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;
}
Tenga en cuenta que en este ejemplo esto no es exactamente una break
-instrucción, pero el bucle for
no hará otra iteración. Si quieres hacer un break
debes insertar una condición if
con la variable continueLoop
que lleva a 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;
}
Simplemente configure una variable global y verifique eso en el ciclo:
#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;
}
break
, como goto
, solo puede saltar localmente dentro de la misma función, pero si es absolutamente necesario, puede usar setjmp
y 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;
}
La llamada a longjmp
causará un salto de regreso a la llamada setjmp
. El valor de retorno de setjmp
muestra si está regresando después de establecer el objetivo de salto, o si está volviendo de un salto.
Salida:
Inside foo!
Jumped out!
Los saltos no locales son seguros cuando se usan correctamente, pero hay una serie de cosas para pensar detenidamente:
- Dado que
longjmp
salta "a través" de todas las activaciones de función entre la llamadasetjmp
y la llamadalongjmp
, si alguna de esas funciones espera poder realizar un trabajo adicional después del lugar actual en ejecución, ese trabajo simplemente no se realizará. - Si la activación de la función que llamó
setjmp
ha terminado, el comportamiento no está definido. Cualquier cosa puede suceder. - Si
setjmp
aún no se ha llamado, entoncesjump_target
no está establecido, y el comportamiento no está definido. - Las variables locales en la función que llamó a
setjmp
pueden bajo ciertas condiciones tener valores indefinidos. - Una palabra: hilos.
- Otras cosas, como los indicadores de estado de coma flotante, pueden no conservarse, y existen restricciones sobre dónde puede colocar la llamada
setjmp
.
La mayoría de estos siguen naturalmente si tiene una buena comprensión de lo que hace un salto no local en el nivel de instrucciones de máquina y registros de CPU, pero a menos que tenga eso y haya leído lo que el estándar C hace y no garantiza, le aconsejaría a algunos precaución.
break
es una declaración que se resuelve durante el tiempo de compilación. Por lo tanto, el compilador debe encontrar el bucle for / while apropiado dentro de la misma función. Tenga en cuenta que no hay garantía de que no se pueda llamar a la función desde otro lugar.