Soy un programador autodidacta. Empecé a programar hace aproximadamente 1,5 años. Ahora he comenzado a tener clases de programación en la escuela. Hemos tenido clases de programación durante 1/2 año y tendremos otro 1/2 en este momento.
En las clases estamos aprendiendo a programar en C ++ (que es un lenguaje que ya sabía que usaba bastante bien antes de comenzar).
No he tenido ninguna dificultad durante esta clase, pero hay un problema recurrente para el que no he podido encontrar una solución clara.
El problema es así (en pseudocódigo):
do something
if something failed:
handle the error
try something (line 1) again
else:
we are done!
Aquí hay un ejemplo en C ++
El código solicita al usuario que ingrese un número y lo hace hasta que la entrada sea válida. Se utiliza cin.fail()
para verificar si la entrada no es válida. Cuándo cin.fail()
es true
que tengo que llamar cin.clear()
y cin.ignore()
poder continuar recibiendo información de la transmisión.
Soy consciente de que este código no verifica
EOF
. No se espera que los programas que hemos escrito hagan eso.
Así es como escribí el código en una de mis tareas en la escuela:
for (;;) {
cout << ": ";
cin >> input;
if (cin.fail()) {
cin.clear();
cin.ignore(512, '\n');
continue;
}
break;
}
Mi maestra dijo que no debería estar usando break
y continue
así. Me sugirió que debería usar un regular while
o un do ... while
bucle en su lugar.
Me parece que usar break
y continue
es la forma más sencilla de representar este tipo de bucle. De hecho, lo pensé durante bastante tiempo, pero realmente no se me ocurrió una solución más clara.
Creo que él quería que yo hiciera algo como esto:
do {
cout << ": ";
cin >> input;
bool fail = cin.fail();
if (fail) {
cin.clear();
cin.ignore(512, '\n');
}
} while (fail);
Para mí, esta versión parece mucho más compleja ya que ahora también tenemos variables llamadas fail
para realizar un seguimiento y la verificación de fallas de entrada se realiza dos veces en lugar de solo una.
También pensé que podía escribir el código de esta manera (abusando de la evaluación de cortocircuito):
do {
cout << ": ";
cin >> input;
if (fail) {
cin.clear();
cin.ignore(512, '\n');
}
} while (cin.fail() && (cin.clear(), cin.ignore(512, '\n', true);
Esta versión funciona exactamente como las otras. No utiliza break
o continue
y la cin.fail()
prueba solo se realiza una vez. Sin embargo, no me parece correcto abusar de la "regla de evaluación de cortocircuito" como esta. Tampoco creo que a mi maestra le guste.
Este problema no solo se aplica solo a la cin.fail()
comprobación. He usado break
y me continue
gusta esto para muchos otros casos que implican repetir un conjunto de código hasta que se cumpla una condición en la que también se debe hacer algo si la condición no se cumple (como llamar cin.clear()
y cin.ignore(...)
del cin.fail()
ejemplo).
He seguido usando break
y continue
durante todo el curso y ahora mi maestro ha dejado de quejarse al respecto.
¿Cuáles son tus opiniones sobre esto?
¿Crees que mi maestro tiene razón?
¿Conoces una mejor manera de representar este tipo de problema?
fuente
while (true)
y al instante asumir que hay unabreak
,return
othrow
en algún lugar allí. La introducción de una variable adicional que debe mantenerse un registro de sólo para esquivar unabreak
ocontinue
es contraproducente. Yo diría que el problema con el código inicial es que el ciclo nunca termina una iteración normalmente, y que necesita saber que hay uncontinue
adentroif
para saberbreak
que no se tomará.dataIsValid
es mutable, por lo que para saber que el ciclo termina correctamente, necesito verificar que esté configurado cuando los datos son válidos, y también que no esté desarmado en ningún momento posterior. En un bucle de la formado { ... if (!expr) { break; } ... } while (true);
, sé que si seexpr
evalúa comofalse
, el bucle se romperá sin tener que mirar el resto del cuerpo del bucle.Respuestas:
Escribiría la declaración if ligeramente diferente, por lo que se toma cuando la entrada es exitosa.
Es más corto también.
Lo que sugiere una forma más corta que podría ser del agrado de tu maestro:
fuente
break
al final. Eso solo hace que el código sea mucho más fácil de leer. Yo tampoco sabía que eso!(cin >> input)
era válido. ¡Gracias por hacérmelo saber!cout << ": ";
, por lo que viola el principio DRY. Para el código del mundo real, esto sería imposible, ya que se vuelve propenso a errores (cuando tenga que cambiar esa parte más adelante, tendrá que asegurarse de no olvidar cambiar dos líneas de manera similar ahora). Sin embargo, te di un +1 para tu primera versión, que en mi humilde opinión es un ejemplo de cómo usarlobreak
correctamente.cout << ": "
completo y nada se rompería.while
comando se ve mejor".Lo que debes esforzarte es evitar los bucles sin procesar .
Mueva la lógica compleja a una función auxiliar y de repente las cosas están mucho más claras:
fuente
getValidUserInput
se llama muchas veces y no solo una. Te doy +1 por eso, pero aceptaré la respuesta de Sjoerd porque encuentro que responde mejor a mi pregunta.No es tanto que
for(;;)
sea malo. Simplemente no es tan claro como patrones como:O como dijo Sjoerd:
Consideremos a la audiencia principal de estas cosas como sus compañeros programadores, incluida la versión futura de usted mismo que ya no recuerda por qué detuvo el descanso al final, o saltó el descanso con la continuación. Este patrón de tu ejemplo:
... requiere un par de segundos de pensamiento extra para simular en comparación con otros patrones. No es una regla difícil y rápida, pero saltar la declaración de ruptura con la continuación se siente desordenado o inteligente sin ser útil, y poner la declaración de ruptura al final del ciclo, incluso si funciona, es inusual. Normalmente ambos
break
ycontinue
se usan para evacuar prematuramente el ciclo o esa iteración del ciclo, por lo que verlos al final se siente un poco extraño, incluso si no lo es.Aparte de eso, ¡cosas buenas!
fuente
s answer: using a
mientras que el bucle de esta manera puede conducir a una duplicación de código innecesaria para el ejemplo dado. Y ese es un problema que, en mi humilde opinión, es al menos tan importante de abordar como la legibilidad general del bucle.while
, creo que el bit "solo a primera vista" no está justificado .while
sintaxis declarativa de carga frontal . No me gusta hacer algo antes del bucle que también se hace dentro del bucle, por otra parte, tampoco me gusta un bucle que se une en nudos tratando de refactorizar el prefijo. Por lo tanto, un poco de un acto de equilibrio de cualquier manera.Mi instructor de lógica en la escuela siempre decía, y lo golpeaba en mi cerebro, que solo debería haber una entrada y una salida a los bucles. Si no, comienza a obtener el código de espagueti y no sabe a dónde va el código durante la depuración.
En la vida real, creo que la legibilidad del código es realmente importante, de modo que si alguien más necesita corregir o depurar un problema con su código, es fácil para ellos hacerlo.
Además, si se trata de un problema de rendimiento porque está ejecutando este aspecto millones de veces, el ciclo for podría ser más rápido. Las pruebas responderían a esa pregunta.
No conozco C ++ pero entendí completamente los dos primeros ejemplos de código. Supongo que continuar va al final del ciclo for y luego lo repite nuevamente. El ejemplo while lo entendí completamente, pero para mí fue más fácil de entender que su ejemplo for (;;). El tercero tendría que hacer un trabajo de reflexión para resolverlo.
Entonces, para mí, que fue más fácil de leer (sin saber c ++) y lo que dijo mi instructor de lógica, usaría el bucle while.
fuente
para mí, la traducción C más directa del pseudocódigo es
No entiendo por qué esta traducción obvia es un problema.
tal vez esto:
eso se ve más simple.
fuente