while sentencia for entre diferencia ciclo bucle c++ c for-loop

c++ - sentencia - ¿Hay alguna razón técnica para usar>(<) en lugar de!=Cuando se incrementa en 1 en un ciclo ''for''?



sentencia break en c (21)

Además de las diversas personas que han mencionado que mitiga el riesgo, también reduce la cantidad de sobrecargas de funciones necesarias para interactuar con varios componentes estándar de la biblioteca. Como ejemplo, si desea que su tipo se pueda almacenar en un std::set , o se use como clave para std::map , o se use con algunos de los algoritmos de búsqueda y clasificación, la biblioteca estándar generalmente usa std::less para comparar objetos, ya que la mayoría de los algoritmos solo necesitan un orden débil estricto. Por lo tanto, se convierte en un buen hábito usar las comparaciones < lugar de != (Donde tiene sentido, por supuesto).

Casi nunca veo un bucle for como este:

for (int i = 0; 5 != i; ++i) {}

¿Hay alguna razón técnica para usar > o < lugar de != Cuando se incrementa en 1 en un ciclo for ? ¿O esto es más una convención?


Además de los ejemplos, donde la variable de bucle cambiará (no intencionalmente) dentro del cuerpo, existen otras razones para usar los operadores más pequeños o más grandes que:

  • Las negaciones hacen que el código sea más difícil de entender
  • < o > es solo un carácter, pero != dos

Como puede ver en las otras numerosas respuestas, hay razones para usar <en lugar de! = Que ayudará en casos extremos, condiciones iniciales, modificación involuntaria del contador de bucle, etc.

Honestamente, sin embargo, no creo que puedas enfatizar la importancia de la convención lo suficiente. Para este ejemplo, será lo suficientemente fácil para que otros programadores vean lo que está tratando de hacer, pero provocará una doble toma. Uno de los trabajos durante la programación es hacerlo lo más legible y familiar posible para todos, por lo que inevitablemente cuando alguien tiene que actualizar / cambiar su código, no se necesita mucho esfuerzo para darse cuenta de lo que estaba haciendo en diferentes bloques de código . Si viera a alguien usar != , Supondría que hubo una razón por la que lo usaron en lugar de < y si se tratara de un bucle grande, revisaría todo el asunto tratando de descubrir qué hiciste que lo hizo necesario. y eso es tiempo perdido.


Como ya dijo Ian Newson, no se puede recorrer de manera confiable una variable flotante y salir con != . Por ejemplo,

for (double x=0; x!=1; x+=0.1) {}

en realidad se repetirá para siempre, porque 0.1 no puede representarse exactamente en coma flotante, por lo tanto, el contador pierde por poco 1. Con < termina.

(Sin embargo, tenga en cuenta que es básicamente un comportamiento indefinido si obtiene 0.9999 ... como el último número aceptado, lo que viola el supuesto menor que, o ya sale a 1.0000000000000001).


Considero que el adjetivo "técnico" significa comportamiento / peculiaridades del lenguaje y efectos secundarios del compilador, como el rendimiento del código generado.

Para este fin, la respuesta es: no (*). El (*) es "consulte el manual de su procesador". Si está trabajando con un sistema RISC o FPGA de caso extremo, es posible que deba verificar qué instrucciones se generan y cuánto cuestan. Pero si está utilizando prácticamente cualquier arquitectura moderna convencional, entonces no hay una diferencia significativa en el nivel de procesador en el costo entre lt , eq , ne y gt .

Si está utilizando un caso de borde, podría encontrar que != Requiere tres operaciones ( cmp , not , beq ) frente a dos ( cmp , blt xtr myo ). De nuevo, RTM en ese caso.

En su mayor parte, las razones son defensivas / endurecimiento, especialmente cuando se trabaja con punteros o bucles complejos. Considerar

// highly contrived example size_t count_chars(char c, const char* str, size_t len) { size_t count = 0; bool quoted = false; const char* p = str; while (p != str + len) { if (*p == ''"'') { quote = !quote; ++p; } if (*(p++) == c && !quoted) ++count; } return count; }

Un ejemplo menos ingenioso sería cuando está utilizando valores de retorno para realizar incrementos, aceptando datos de un usuario:

#include <iostream> int main() { size_t len = 5, step; for (size_t i = 0; i != len; ) { std::cout << "i = " << i << ", step? " << std::flush; std::cin >> step; i += step; // here for emphasis, it could go in the for(;;) } }

Pruebe esto e ingrese los valores 1, 2, 10, 999.

Podrías evitar esto:

#include <iostream> int main() { size_t len = 5, step; for (size_t i = 0; i != len; ) { std::cout << "i = " << i << ", step? " << std::flush; std::cin >> step; if (step + i > len) std::cout << "too much./n"; else i += step; } }

Pero lo que probablemente querías era

#include <iostream> int main() { size_t len = 5, step; for (size_t i = 0; i < len; ) { std::cout << "i = " << i << ", step? " << std::flush; std::cin >> step; i += step; } }

También hay una especie de sesgo de convención hacia < , porque ordenar en contenedores estándar a menudo depende del operator< , por ejemplo, el hash en varios contenedores STL determina la igualdad al decir

if (lhs < rhs) // T.operator < lessthan else if (rhs < lhs) // T.operator < again greaterthan else equal

Si lhs y rhs son una clase definida por el usuario que escribe este código como

if (lhs < rhs) // requires T.operator< lessthan else if (lhs > rhs) // requires T.operator> greaterthan else equal

El implementador tiene que proporcionar dos funciones de comparación. Entonces < ha convertido en el operador favorito.


El uso de comparaciones relacionales en tales casos es más un hábito popular que cualquier otra cosa. Ganó su popularidad en los tiempos en que las consideraciones conceptuales como las categorías de iterador y su comparabilidad no se consideraban de alta prioridad.

Yo diría que uno debería preferir usar comparaciones de igualdad en lugar de comparaciones relacionales siempre que sea posible, ya que las comparaciones de igualdad imponen menos requisitos a los valores que se comparan. Ser EqualityComparable es un requisito menor que ser LessThanComparable .

Otro ejemplo que demuestra la aplicabilidad más amplia de la comparación de igualdad en tales contextos es el enigma popular con la implementación de la iteración unsigned hasta 0 . Se puede hacer como

for (unsigned i = 42; i != -1; --i) ...

Tenga en cuenta que lo anterior es igualmente aplicable a la iteración con signo y sin signo, mientras que la versión relacional se descompone con tipos sin signo.


Hay dos razones relacionadas para seguir esta práctica que tienen que ver con el hecho de que un lenguaje de programación es, después de todo, un lenguaje que será leído por los humanos (entre otros).

(1) Un poco de redundancia. En lenguaje natural, generalmente proporcionamos más información de la estrictamente necesaria, como un código de corrección de errores. Aquí la información adicional es que la variable de bucle i (¿ve cómo utilicé la redundancia aquí? Si no sabía qué significa ''variable de bucle'', o si olvidó el nombre de la variable, después de leer "variable de bucle i " tiene la información completa) es inferior a 5 durante el ciclo, no solo diferente de 5. La redundancia mejora la legibilidad.

(2) Convención. Los idiomas tienen formas estándar específicas de expresar ciertas situaciones. Si no sigue la forma establecida de decir algo, aún será entendido, pero el esfuerzo para el destinatario de su mensaje es mayor porque ciertas optimizaciones no funcionarán. Ejemplo:

No hables de la mezcla caliente. ¡Solo ilumina la dificultad!

La primera oración es una traducción literal de un idioma alemán. El segundo es un idioma común en inglés con las palabras principales reemplazadas por sinónimos. El resultado es comprensible, pero lleva mucho más tiempo comprenderlo que esto:

No andes por las ramas. ¡Solo explica el problema!

Esto es cierto incluso en el caso de que los sinónimos utilizados en la primera versión se ajusten mejor a la situación que las palabras convencionales en el idioma inglés. Fuerzas similares están vigentes cuando los programadores leen el código. Esta es también la razón por la cual 5 != i y 5 > i son formas extrañas de decirlo a menos que esté trabajando en un entorno en el que es estándar intercambiar los más normales i != 5 e i < 5 de esta manera. Tales comunidades de dialectos existen, probablemente porque la consistencia hace que sea más fácil recordar escribir 5 == i lugar del natural pero propenso a errores i == 5 .


Hay varias formas de escribir cualquier tipo de código (generalmente), en este caso solo hay dos formas (tres si cuenta <= y> =).

En este caso, las personas prefieren> y <para asegurarse de que incluso si sucede algo inesperado en el bucle (como un error), no se repetirá infinitamente (MALO). Considere el siguiente código, por ejemplo.

for (int i = 1; i != 3; i++) { //More Code i = 5; //OOPS! MISTAKE! //More Code }

Si usáramos (i <3), estaríamos a salvo de un bucle infinito porque coloca una restricción más grande.

Realmente es su elección si desea un error en su programa para cerrar todo o seguir funcionando con el error allí.

Espero que esto haya ayudado!


La condición de bucle es un bucle forzado invariante.

Supongamos que no miras el cuerpo del bucle:

for (int i = 0; i != 5; ++i) { // ? }

en este caso, usted sabe al comienzo de la iteración del ciclo que no es igual a 5 .

for (int i = 0; i < 5; ++i) { // ? }

En este caso, usted sabe al comienzo de la iteración del bucle que i es menor que 5 .

El segundo es mucha, mucha más información que el primero, ¿no? Ahora, la intención del programador es (casi con certeza) la misma, pero si está buscando errores, tener confianza al leer una línea de código es algo bueno. Y el segundo hace cumplir esa invariante, lo que significa que algunos errores que podrían morderlo en el primer caso simplemente no pueden suceder (o no causar corrupción de memoria, por ejemplo) en el segundo caso.

Usted sabe más sobre el estado del programa, al leer menos código, con < que con != . Y en las CPU modernas, toman la misma cantidad de tiempo que ninguna diferencia.

Si su i no fue manipulado en el cuerpo del bucle, y siempre se incrementó en 1, y comenzó menos de 5 , no habría diferencia. Pero para saber si fue manipulado, tendría que confirmar cada uno de estos hechos.

Algunos de estos hechos son relativamente fáciles, pero puede equivocarse. Sin embargo, verificar todo el cuerpo del asa es un dolor.

En C ++ puede escribir un tipo de indexes tal que:

for( const int i : indexes(0, 5) ) { // ? }

hace lo mismo que cualquiera de los dos anteriores for bucles, incluso hasta el compilador optimizándolo al mismo código. Aquí, sin embargo, sabes que no puedo ser manipulado en el cuerpo del bucle, ya que se declara const , sin que el código corrompa la memoria.

Cuanta más información pueda obtener de una línea de código sin tener que comprender el contexto, más fácil será rastrear lo que va mal. < en el caso de los bucles enteros le brinda más información sobre el estado del código en esa línea que != hace.


La razón más común para usar < es la convención. Más programadores piensan en bucles como este como "mientras el índice está dentro del rango" en lugar de "hasta que el índice llegue al final". Hay un valor que se apega a la convención cuando puedes.

Por otro lado, muchas respuestas aquí afirman que usar el formulario < ayuda a evitar errores. Yo diría que en muchos casos esto solo ayuda a ocultar errores. Si se supone que el índice de bucle alcanza el valor final y, en cambio, en realidad va más allá, entonces está sucediendo algo que no esperaba que puede causar un mal funcionamiento (o ser un efecto secundario de otro error). El < probablemente retrasará el descubrimiento del error. Es más probable que != Provoque un bloqueo, un bloqueo o incluso un bloqueo, lo que lo ayudará a detectar el error antes. Cuanto antes se encuentre un error, más barato será repararlo.

Tenga en cuenta que esta convención es peculiar de la indexación de matrices y vectores. Al atravesar casi cualquier otro tipo de estructura de datos, usaría un iterador (o puntero) y buscaría directamente un valor final. En esos casos, debe asegurarse de que el iterador alcance y no sobrepase el valor final real.

Por ejemplo, si está recorriendo una cadena C simple, generalmente es más común escribir:

for (char *p = foo; *p != ''/0''; ++p) { // do something with *p }

que

int length = strlen(foo); for (int i = 0; i < length; ++i) { // do something with foo[i] }

Por un lado, si la cadena es muy larga, la segunda forma será más lenta porque la strlen es otro paso a través de la cadena.

Con un C ++ std :: string, usaría un bucle for basado en rango, un algoritmo estándar o iteradores, incluso si la longitud está fácilmente disponible. Si usa iteradores, la convención es usar != lugar de < , como en:

for (auto it = foo.begin(); it != foo.end(); ++it) { ... }

Del mismo modo, iterar un árbol o una lista o una eliminación generalmente implica buscar un puntero nulo u otro centinela en lugar de verificar si un índice permanece dentro de un rango.


Los iteradores son un caso importante cuando usas con más frecuencia la notación != :

for(auto it = vector.begin(); it != vector.end(); ++it) { // do stuff }

De acuerdo: en la práctica, escribiría lo mismo confiando en un range-for :

for(auto & item : vector) { // do stuff }

pero el punto permanece: uno normalmente compara iteradores usando == o != .


No hay problema desde una perspectiva de sintaxis, pero la lógica detrás de esa expresión 5!=i no es sólida.

En mi opinión, usar != Para establecer los límites de un bucle for no es lógico porque un bucle for aumenta o disminuye el índice de iteración, por lo que configurar el bucle para iterar hasta que el índice de iteración se salga de los límites ( != algo ) no es una implementación adecuada.

Funcionará, pero es propenso al mal comportamiento ya que el manejo de datos de límites se pierde cuando se usa != Para un problema incremental (lo que significa que sabe desde el principio si aumenta o disminuye), es por eso que en lugar de != El <>>==> se utilizan.


No hay razón técnica. Pero existe una mitigación de riesgos, mantenibilidad y una mejor comprensión del código.

< o > son restricciones más fuertes que != y cumplen exactamente el mismo propósito en la mayoría de los casos (incluso diría que en todos los casos prácticos).

Hay una pregunta duplicada here ; y una answer interesante


Puede suceder que la variable i esté establecida en algún valor grande y si solo usa el operador != Terminará en un bucle sin fin.


Puedes tener algo como

for(int i = 0; i<5; ++i){ ... if(...) i++; ... }

Si su variable de bucle está escrita por el código interno, i!=5 podría no romper ese bucle. Esto es más seguro para verificar la desigualdad.

Edita sobre legibilidad. La forma de desigualdad se usa con mayor frecuencia. Por lo tanto, esto es muy rápido de leer ya que no hay nada especial que entender (la carga cerebral se reduce porque la tarea es común). Por lo tanto, es genial que los lectores hagan uso de estos hábitos.


Sí hay una razón Si escribe un bucle for (simple basado en un índice antiguo) como este

for (int i = a; i < b; ++i){}

entonces funciona como se esperaba para cualquier valor de a y b (es decir, cero iteraciones cuando a > b lugar de infinito si hubiera usado i == b; ).

Por otro lado, para iteradores escribirías

for (auto it = begin; it != end; ++it)

porque cualquier iterador debe implementar un operator!= , pero no para cada iterador es posible proporcionar un operator< .

También basado en rango para bucles

for (auto e : v)

no son solo azúcar sofisticada, sino que reducen considerablemente las posibilidades de escribir código incorrecto.


Si; OpenMP no paraleliza bucles con la condición != .


Una razón para no usar esta construcción son los números de coma flotante. != es una comparación muy peligrosa para usar con flotadores, ya que rara vez se evaluará como verdadero incluso si los números se ven iguales. < o > elimina este riesgo.


Y por último, pero no menos importante, esto se llama programación defensiva , lo que significa tomar siempre el caso más fuerte para evitar errores actuales y futuros que influyen en el programa.

El único caso en el que no se necesita programación defensiva es donde los estados han sido probados por condiciones previas y posteriores (pero luego, probar que esta es la más defensiva de toda la programación).


Yo diría que una expresión como

for ( int i = 0 ; i < 100 ; ++i ) { ... }

es más expresivo de intención de lo que es

for ( int i = 0 ; i != 100 ; ++i ) { ... }

El primero claramente dice que la condición es una prueba para un límite superior exclusivo en un rango; este último es una prueba binaria de una condición de salida. Y si el cuerpo del bucle no es trivial, puede que no sea evidente que el índice solo se modifica en la declaración for .


while (time != 6:30pm) { Work(); }

Son las 6:31 pm ... Maldición, ¡ahora mi próxima oportunidad de ir a casa es mañana! :)

Esto para mostrar que la restricción más fuerte mitiga los riesgos y es probablemente más intuitiva de entender.