¿Alguna vez se necesita un ciclo “do {…} while ()”?

Bjarne Stroustrup (creador de C ++) dijo una vez que evita los bucles “do / while”, y prefiere escribir el código en términos de un bucle “while”. [Ver cita a continuación.]

Desde que escuché esto, he encontrado que esto es cierto. ¿Cuáles son tus pensamientos? ¿Hay algún ejemplo en el que un “do / while” sea mucho más limpio y fácil de entender que si usara un “while” en su lugar?

En respuesta a algunas de las respuestas: sí, entiendo la diferencia técnica entre “do / while” y “while”. Esta es una pregunta más profunda sobre la legibilidad y el código de estructuración que involucra bucles.

Déjame preguntarte de otra manera: supongamos que tienes prohibido usar “do / while” – ¿hay algún ejemplo realista en el que esto no te dé más remedio que escribir un código sucio usando “while”?

De “El lenguaje de progtwigción de C ++”, 6.3.3:

En mi experiencia, la statement do es una fuente de errores y confusión. La razón es que su cuerpo siempre se ejecuta una vez antes de que se evalúe la condición. Sin embargo, para que el cuerpo funcione correctamente, algo muy parecido a la condición debe mantenerse incluso la primera vez. Con más frecuencia de lo que hubiera adivinado, he encontrado que esa condición no se mantiene como se esperaba ya sea cuando se escribió y se probó por primera vez el progtwig, o más tarde después de que se modificó el código que lo precedió. También prefiero la condición “desde el principio, donde puedo verlo”. En consecuencia, tiendo a evitar las declaraciones de do. -Bjarne

Sí, estoy de acuerdo con que los bucles while se pueden volver a escribir en un ciclo while, sin embargo, no estoy de acuerdo con que siempre sea mejor usar un ciclo while. hacer mientras siempre se ejecuta al menos una vez y que es una propiedad muy útil (el ejemplo más típico es la comprobación de entrada (desde el teclado))

#include  int main() { char c; do { printf("enter a number"); scanf("%c", &c); } while (c < '0' || c > '9'); } 

Por supuesto, esto se puede reescribir en un ciclo while, pero esto generalmente se ve como una solución mucho más elegante.

do-while es un ciclo con una condición posterior. Lo necesita en los casos en que el cuerpo del bucle se debe ejecutar al menos una vez. Esto es necesario para el código que necesita alguna acción antes de que la condición de bucle pueda evaluarse con sensatez. Con while loop deberías llamar al código de inicialización desde dos sitios, con do-while, solo puedes llamar desde un sitio.

Otro ejemplo es cuando ya tiene un objeto válido cuando se va a iniciar la primera iteración, por lo que no desea ejecutar nada (evaluación de la condición del ciclo incluida) antes de que comience la primera iteración. Un ejemplo es con las funciones FindFirstFile / FindNextFile Win32: llama a FindFirstFile que devuelve un error o un identificador de búsqueda al primer archivo, luego llama a FindNextFile hasta que devuelve un error.

Pseudocódigo:

 Handle handle; Params params; if( ( handle = FindFirstFile( params ) ) != Error ) { do { process( params ); //process found file } while( ( handle = FindNextFile( params ) ) != Error ) ); } 

do { ... } while (0) es una construcción importante para hacer que las macros se comporten bien.

Incluso si no es importante en el código real (con el que no necesariamente estoy de acuerdo), es importante para corregir algunas de las deficiencias del preprocesador.

Editar: me encontré con una situación en la que do / while estaba mucho más limpio hoy en mi propio código. Estaba haciendo una abstracción multiplataforma de las instrucciones combinadas LL / SC . Estos deben ser utilizados en un bucle, así:

 do { oldvalue = LL (address); newvalue = oldvalue + 1; } while (!SC (address, newvalue, oldvalue)); 

(Los expertos podrían darse cuenta de que oldvalue no se utiliza en una implementación de SC, pero está incluido para que esta abstracción se pueda emular con CAS).

LL y SC son un excelente ejemplo de una situación en la que do / while es significativamente más limpio que el equivalente mientras se forma:

 oldvalue = LL (address); newvalue = oldvalue + 1; while (!SC (address, newvalue, oldvalue)) { oldvalue = LL (address); newvalue = oldvalue + 1; } 

Por esta razón, estoy extremadamente decepcionado por el hecho de que Google Go haya optado por eliminar el constructo do-while .

Es útil cuando quiere “hacer” algo “hasta” que se cumpla una condición.

Se puede mezclar en un ciclo while como este:

 while(true) { // .... code ..... if(condition_satisfied) break; } 

(Suponiendo que sepa la diferencia entre los dos)

Do / While es bueno para arrancar / preiniciar el código antes de que se compruebe su condición y se ejecute el ciclo while.

En nuestras convenciones de encoding

  • si / while / … las condiciones no tienen efectos secundarios y
  • las varibles deben ser inicializadas.

Así que casi nunca do {} while(xx) un do {} while(xx) Porque:

 int main() { char c; do { printf("enter a number"); scanf("%c", &c); } while (c < '0' || c > '9'); } 

está reescrito en:

 int main() { char c(0); while (c < '0' || c > '9'); { printf("enter a number"); scanf("%c", &c); } } 

y

 Handle handle; Params params; if( ( handle = FindFirstFile( params ) ) != Error ) { do { process( params ); //process found file } while( ( handle = FindNextFile( params ) ) != Error ) ); } 

está reescrito en:

 Params params(xxx); Handle handle = FindFirstFile( params ); while( handle!=Error ) { process( params ); //process found file handle = FindNextFile( params ); } 

El siguiente lenguaje común me parece muy sencillo:

 do { preliminary_work(); value = get_value(); } while (not_valid(value)); 

La reescritura para evitar do parece ser:

 value = make_invalid_value(); while (not_valid(value)) { preliminary_work(); value = get_value(); } 

Esa primera línea se usa para garantizar que la prueba siempre se evalúe como verdadera la primera vez. En otras palabras, la prueba siempre es superflua la primera vez. Si esta prueba superflua no estuviera allí, también se podría omitir la asignación inicial. Este código da la impresión de que lucha por sí mismo.

En casos como estos, la construcción do es una opción muy útil.

Se trata de la legibilidad .
Un código más legible produce menos dolor de cabeza en el mantenimiento del código y una mejor colaboración.
Otras consideraciones (como la optimización) son, con mucho, menos importantes en la mayoría de los casos.
Voy a elaborar, ya que tengo un comentario aquí:
Si tiene un fragmento de código A que usa do { ... } while() , y es más legible que su while() {...} equivalente B , entonces votaría por A. Si prefiere B , ya que ve la condición de bucle “por adelantado”, y cree que es más legible (y, por lo tanto, se puede mantener, etc.) – luego continúe, use B.
Mi punto es: usa el código que sea más legible para tus ojos (y para tus colegas). La elección es subjetiva, por supuesto.

Es solo una elección personal en mi opinión.

La mayoría de las veces, puede encontrar una forma de reescribir un do … while loop en un ciclo while; pero no necesariamente siempre También podría tener un sentido más lógico escribir un ciclo de hacer mientras se ajusta al contexto en el que se encuentra.

Si miras arriba, la respuesta de TimW, habla por sí misma. El segundo con Handle, especialmente es más complicado en mi opinión.

Esta es la alternativa más limpia para hacer, mientras que la he visto. Es la expresión idiomática recomendada para Python que no tiene un ciclo do-while.

Una advertencia es que no puede continue en el ya que saltaría la condición de interrupción, pero ninguno de los ejemplos que muestran los beneficios del do-while necesita continuar antes de la condición.

 while (true) {  if (!condition) break;  } 

Aquí se aplica a algunos de los mejores ejemplos de los bucles do-while anteriores.

 while (true); { printf("enter a number"); scanf("%c", &c); if (!(c < '0' || c > '9')) break; } 

El siguiente ejemplo es un caso en el que la estructura es más legible que un do-while, ya que la condición se mantiene cerca de la parte superior, ya que //get data suele ser corto, aunque //process data parte de //process data puede ser larga.

 while (true); { // get data if (data == null) break; // process data // process it some more // have a lot of cases etc. // wow, we're almost done. // oops, just one more thing. } 

Casi nunca los uso simplemente por lo siguiente:

Aunque el ciclo comprueba si hay una condición posterior, aún necesita verificar esta condición de publicación dentro de su ciclo para que no procese la condición de la publicación.

Tome el pseudo código de muestra:

 do { // get data // process data } while (data != null); 

Suena simple en teoría, pero en situaciones del mundo real probablemente resultaría así:

 do { // get data if (data != null) { // process data } } while (data != null); 

El cheque adicional “si” no vale la pena IMO. He encontrado muy pocos casos en los que es más lacónico hacer un ciclo “do-while” en lugar de “while”. YMMV.

En respuesta a una pregunta / comentario de desconocido (google) a la respuesta de Dan Olson:

“do {…} while (0) es una construcción importante para hacer que las macros se comporten bien”.

 #define M do { doOneThing(); doAnother(); } while (0) ... if (query) M; ... 

¿Ves lo que sucede sin el do { ... } while(0) ? Siempre ejecutará doAnother ().

Lea el Teorema del Progtwig Estructurado . A do {} while () siempre se puede reescribir a while () do {}. Secuencia, selección e iteración son todo lo que se necesita.

Como todo lo que está contenido en el cuerpo del bucle siempre se puede encapsular en una rutina, la suciedad de tener que usar while () do {} nunca debe empeorar

 LoopBody() while(cond) { LoopBody() } 

Un ciclo do-while siempre se puede reescribir como un ciclo while.

Ya sea para usar solo while while, o while, do-while y for-loops (o cualquier combinación de ambos) depende en gran medida de su gusto por la estética y las convenciones del proyecto en el que está trabajando.

Personalmente, prefiero while-loops porque simplifica el razonamiento sobre invariantes de bucle en mi humilde opinión.

En cuanto a si hay situaciones en las que se necesitan bucles para hacer: en lugar de

 do { loopBody(); } while (condition()); 

tu siempre puedes

 loopBody(); while(condition()) { loopBody(); } 

entonces, no, nunca necesitas usar do-while si no puedes por alguna razón. (Por supuesto, este ejemplo viola DRY, pero es solo una prueba de concepto. En mi experiencia, generalmente hay una forma de transformar un ciclo do-while en un ciclo while y no violar DRY en ningún caso de uso concreto).

“Cuando en Roma, haz como los romanos.”

Por cierto: la cita que está buscando es tal vez esta ([1], último párrafo de la sección 6.3.3):

Desde mi experiencia, la statement do es una fuente de error y confusión. La razón es que su cuerpo siempre se ejecuta una vez antes de que se pruebe la condición. Para el correcto funcionamiento del cuerpo, sin embargo, una condición similar a la condición final debe mantenerse en la primera ejecución. Con más frecuencia de lo que esperaba, he encontrado que estas condiciones no son ciertas. Este fue el caso cuando escribí el progtwig en cuestión desde cero y luego lo probé así como después de un cambio de código. Además, prefiero la condición “por adelantado, donde puedo verlo”. Por lo tanto, tiendo a evitar hacer declaraciones.

(Nota: esta es mi traducción de la edición alemana. Si posee la edición en inglés, siéntase libre de editar la cita para que coincida con su redacción original. Desafortunadamente, Addison-Wesley odia a Google).

[1] B. Stroustrup: el lenguaje de progtwigción C ++. 3ra Edición. Addison-Wessley, Reading, 1997.

Antes que nada, estoy de acuerdo en que do-while es menos legible que hacerlo.

Pero me sorprende que después de tantas respuestas, nadie haya considerado por do-while aunque exista en el lenguaje. La razón es eficiencia.

Digamos que tenemos un ciclo do-while while con verificaciones de condición N , donde el resultado de la condición depende del cuerpo del ciclo. Entonces, si lo reemplazamos con un ciclo while, obtenemos en su lugar verificaciones de condición N+1 , donde la verificación adicional no tiene sentido. No es gran cosa si la condición de bucle solo contiene una comprobación de un valor entero, pero digamos que tenemos

 something_t* x = NULL; while( very_slowly_check_if_something_is_done(x) ) { set_something(x); } 

Entonces, la llamada de función en la primera vuelta del ciclo es redundante: ya sabemos que x no está configurado para nada todavía. Entonces, ¿por qué ejecutar algún código indirecto sin sentido?

A menudo uso do-while para este propósito al codificar sistemas embebidos en tiempo real, donde el código dentro de la condición es relativamente lento (verificando la respuesta de algún periférico de hardware lento).

considera algo como esto:

 int SumOfString(char* s) { int res = 0; do { res += *s; ++s; } while (*s != '\0'); } 

Ocurre que ‘\ 0’ es 0, pero espero que entiendas el punto.

Mi problema con do / while es estrictamente con su implementación en C. Debido a la reutilización de la palabra clave while , a menudo hace tropezar a la gente porque parece un error.

Si while hubiera sido reservado solo para while loops y do / while hubieran sido cambiados a do / until o repeat / until , no creo que el loop (que sea ciertamente útil y la forma “correcta” de codificar algunos loops) cause tanto problema.

Ya he hablado antes sobre esto con respecto a JavaScript , que también heredó esta lamentable elección de C.

Bueno, tal vez esto se remonta unos pocos pasos, pero en el caso de

 do { output("enter a number"); int x = getInput(); //do stuff with input }while(x != 0); 

Sería posible, aunque no necesariamente legible de usar

 int x; while(x = getInput()) { //do stuff with input } 

Ahora, si quisiera usar un número distinto de 0 para salir del ciclo

 while((x = getInput()) != 4) { //do stuff with input } 

Pero, de nuevo, hay una pérdida en la legibilidad, sin mencionar que se considera una mala práctica utilizar una statement de asignación dentro de un condicional, solo quería señalar que hay formas más compactas de hacerlo que asignar un valor “reservado” para indicar al ciclo que es el recorrido inicial.

Me gusta el ejemplo de David Božjak. Sin embargo, para hacer de abogado del diablo, creo que siempre puedes factorizar el código que deseas ejecutar al menos una vez en una función separada, logrando tal vez la solución más legible. Por ejemplo:

 int main() { char c; do { printf("enter a number"); scanf("%c", &c); } while (c < '0' || c > '9'); } 

podría convertirse en esto:

 int main() { char c = askForCharacter(); while (c < '0' || c > '9') { c = askForCharacter(); } } char askForCharacter() { char c; printf("enter a number"); scanf("%c", &c); return c; } 

(Perdonar cualquier syntax incorrecta, no soy un progtwigdor de C)