2008-09-25 27 views
5

Estas bucles for se encuentran entre los primeros ejemplos básicos de pruebas formales de corrección de algoritmos. Tienen diferentes pero equivalentes condiciones de terminación:Condiciones de terminación de bucle

1 for (int i = 0; i != N; ++i) 

2 for (int i = 0; i < N; ++i) 

La diferencia se hace evidente en las condiciones posteriores:

  • El primero da la garantía fuerte que i == N después de que el ciclo termina.

  • El segundo solo da la débil garantía de que i >= N termina el ciclo, pero se sentirá tentado a suponer que i == N.

Si por alguna razón el incremento ++i se cambia cada vez que algo así como i += 2, o si i es modificado dentro del bucle, o si N es negativo, el programa puede fallar:

  • El el primero puede quedarse atascado en un ciclo infinito. Falla temprano, en el ciclo que tiene el error. La depuración es fácil.

  • El segundo ciclo finalizará, y en algún momento posterior el programa puede fallar debido a su suposición incorrecta de i == N. Puede fallar muy lejos del bucle que causó el error, lo que hace difícil rastrearlo. O puede continuar silenciosamente haciendo algo inesperado, lo que es aún peor.

¿Qué condición de terminación prefiere, y por qué? ¿Hay otras consideraciones? ¿Por qué muchos programadores que saben esto se niegan a aplicarlo?

+1

Para aquellos que mencionaron que no estoy disponible fuera del, es útil tener en cuenta que el valor de i en la terminación se puede utilizar al razonar sobre programas. Por ejemplo, si desea establecer P (N) con un bucle que mantenga invariante P (i), entonces i == N le da P (N) mientras que i> = N no. – mweerden

+0

+1 por una pregunta bien escrita que exprese claramente los méritos de cada opción. –

Respuesta

1

En C++, se prefiere usar la prueba != como generalidad. Los iteradores en C++ tienen varios conceptos, como input iterator, forward iterator, bidirectional iterator, random access iterator, cada uno de los cuales amplía el anterior con nuevas capacidades. Para que < funcione, se requiere un iterador de acceso aleatorio, mientras que != simplemente requiere un iterador de entrada.

2

No deberíamos mirar el contador de forma aislada: si por alguna razón alguien cambiara la forma en que se incrementa el contador, cambiarían las condiciones de terminación y la lógica resultante si es necesario para i == N.

Preferiría la segunda condición, ya que es más estándar y no dará como resultado un bucle infinito.

1

Si confía en su código, puede hacerlo.

Si quiere que su código sea legible y fácil de entender (y por lo tanto más tolerante al cambio de alguien que suponga que es un klutz), usaría algo así como;

for (int i = 0 ; i >= 0 && i < N ; ++i) 
1

siempre uso # 2 como entonces usted puede estar seguro el bucle terminará ... Confiar en que sea igual a N después, se basa en un efecto secundario ... ¿No te ser mejor usar la variable N en sí?

[edit] Disculpe ...Me refería # 2

0

En general yo preferiría

for (int i = 0; i < N; ++i) 

El castigo para un programa con errores en la producción, parece mucho menos severa, que no tendrá un hilo atrapado para siempre en un bucle, una situación eso puede ser muy arriesgado y muy difícil de diagnosticar.

Además, en general me gusta evitar este tipo de bucles en favor de los bucles de estilo foreach más legibles.

1

Creo que la mayoría de los programadores usan el segundo, porque ayuda a descubrir qué sucede dentro del ciclo. Puedo verlo y "saber" que comenzaré como 0, y definitivamente será menor que N.

La primera variante no tiene esta calidad. Puedo verlo, y todo lo que sé es que comenzaré como 0 y que nunca será igual a N. No es tan útil.

Independientemente de cómo finalice el bucle, siempre es bueno ser muy cuidadoso de utilizar una variable de control de bucle fuera del bucle. En sus ejemplos, declara (correctamente) i dentro del ciclo, por lo que no está dentro del alcance fuera del ciclo y la cuestión de su valor es discutible ...

Por supuesto, la segunda variante también tiene la ventaja de que es lo que todas las referencias C que he visto usan :-)

0

Prefiero usar # 2, solo porque intento no extender el significado de i fuera del ciclo for. Si estuviera rastreando una variable como esa, crearía una prueba adicional. Algunos pueden decir que esto es redundante o ineficiente, sino que recuerda al lector de mi intención: En este punto, i debe ser igual a N

@timyates - Estoy de acuerdo uno no debe depender de los efectos secundarios

4

Tiendo a usar la segunda forma, simplemente porque entonces puedo estar más seguro de que el ciclo terminará. Es decir. es más difícil introducir un error sin terminación al alterar i dentro del ciclo.

Por supuesto, también tiene la ventaja ligeramente perezoso de ser un carácter menos para escribir;)

También me discutir, que en un lenguaje con reglas de ámbito sensibles, como i se declara dentro de la construcción de lazo, no debería estar disponible fuera del ciclo. Esto mitigaría cualquier dependencia de que yo sea igual a N al final del ciclo ...

+0

Estaba pensando lo mismo, seguramente la mayoría de los idiomas tendrían solo en el alcance del ciclo. +1 –

+0

En Perl: 'para (mi $ i = 0; $ i

0

Creo que usted indicó muy bien la diferencia entre los dos. Tengo los siguientes comentarios, sin embargo:

  • Esto no es "independiente del idioma", puedo ver sus ejemplos están en C++ pero no son los idiomas en los que no está autorizado a modificar la variable de bucle dentro de la bucle y otros que no garantizan que el valor del índice se pueda utilizar después de el bucle (y algunos lo hacen ambos).

  • Usted ha declarado el índice i dentro del for por lo que no sería apostar por el valor de i después del bucle. Los ejemplos son un poco engañosos ya que suponen implícitamente que for es un bucle definido.En realidad, es sólo una forma más conveniente de la escritura:

    // version 1 
    { int i = 0; 
        while (i != N) { 
        ... 
        ++i; 
        } 
    } 
    

    Nota cómo i es indefinido tras el bloque.

Si un programador sabía todo lo anterior no tendría suposición general del valor de i y sería bastante conveniente elegir i<N como las condiciones finales, para asegurarse de que la condición de la salida será finalmente se reunió.

0

El uso de cualquiera de los anteriores en C# podría causar un error de compilación si se ha utilizado i fuera del bucle

0

esto a veces prefiero:

for (int i = 0; (i <= (n-1)); i++) { ... } 

Esta versión muestra directamente el rango de valores que puedo tener. Mi intento de verificar el límite superior e inferior del rango es que si realmente lo necesita, su código tiene demasiados efectos secundarios y necesita ser reescrito.

La otra versión:

for (int i = 1; (i <= n); i++) { ... } 

ayuda a determinar con qué frecuencia el cuerpo del bucle se llama. Esto también tiene casos de uso válidos.

0

Para el trabajo de programación general prefiero

for (int i = 0; i < N; ++i) 

a

for (int i = 0; i != N; ++i) 

Debido a que es menos propenso a errores, especialmente cuando el código se refactorizado. He visto este tipo de código convertido en un ciclo infinito por accidente.

Ese argumento hizo que "tengas la tentación de asumir que i == N", no creo que sea cierto. Nunca he hecho esa suposición o he visto a otro programador hacerlo.

0

Desde mi punto de vista de la verificación formal y el análisis de finalización automática, yo prefiero # 2 (<). Es bastante fácil seguir que alguna variable se incrementa (antes de var = x, después de var = x+n para un número no negativo n). Sin embargo, no es tan fácil ver que i==N finalmente se mantiene. Para esto, uno necesita inferir que i se incrementa exactamente en 1 en cada paso, que (en ejemplos más complicados) podría perderse debido a la abstracción.

Si piensa en el bucle que se incrementa en dos (i = i + 2), esta idea general se vuelve más comprensible. Para garantizar la terminación, ahora debe saber que i%2 == N%2, mientras que esto es irrelevante cuando se usa < como condición.

Cuestiones relacionadas