c++ - switch - C for loop implementado de manera diferente que otros lenguajes?
for c++ (11)
Leí lo siguiente en una revisión de Knuth''s "The Art of Computer Programming":
"La misma ''practicidad'' significa que el aspirante a CS debe aprender los errores de Kernighan al diseñar C, especialmente el infame hecho de que un bucle for evalúa la condición para repetidamente, lo que duplica y no coincide con el comportamiento de la mayoría de los otros idiomas que implementan un bucle for ".
( http://www.amazon.com/review/R9OVJAJQCP78N/ref=cm_cr_pr_viewpnt#R9OVJAJQCP78N )
¿De qué está hablando éste chico? ¿Cómo podría implementar un bucle for que no era solo azúcar sintáctico durante un ciclo while?
Básicamente, el C (y Java, JavaScript, y la gran cantidad de lenguajes derivados de C) for
loop es de hecho un azúcar sintáctico para un ciclo while:
for (int i = 0; i < max; i++) { DoStuff(); }
que es un modismo muy actual es estrictamente equivalente a:
int i = 0; while (i < max) { DoStuff(); i++; }
(Dejando a un lado los problemas de alcance, que cambiaron entre las versiones de C de todos modos).
La condición de detención se evalúa en cada iteración. Puede ser interesante en algunos casos, pero puede ser un escollo: vi i < strlen(longConstantString)
en una fuente, que es una forma importante de ralentizar un programa, porque strlen
es una función costosa en C.
De alguna manera, el bucle for
está diseñado principalmente para ejecutarse varias veces, saber de antemano, aún puede usar break
para terminar temprano, por lo que la evaluación dinámica del término de parada es más molesta que útil: si realmente necesita una evaluación dinámica, use while () {}
(o do {} while ()
).
En algunos otros lenguajes, como Lua, la condición de detención se evalúa solo una vez, en el ciclo init. Ayuda a predecir el comportamiento del bucle y a menudo es más eficiente.
Considera esto:
for i:=0 to 100 do { ... }
En este caso, podríamos reemplazar el valor final, 100, por una llamada a función:
for i:=0 to final_value() do { ... }
... y la final_value
final_value se llamaría solo una vez.
En C, sin embargo:
for (int i=0; i<final_value(); ++i) // ...
... se final_value
la final_value
final_value para cada iteración a través del ciclo, por lo que es una buena práctica ser más detallado:
int end = final_value();
for (int i=0; i<end; ++i) // ...
En Ada (y creo que en la mayoría de los otros lenguajes derivados de Algol), la condición de terminación de un ciclo "for" se evalúa solo una vez, al comienzo del ciclo. Por ejemplo, supongamos que tienes el siguiente código en Ada
q := 10;
for i in 1..q loop
q := 20;
--// Do some stuff
end loop;
Este ciclo repetirá exactamente 10 veces, porque q era 10 cuando comenzó el ciclo. Sin embargo, si escribo el bucle aparentemente equivalente en C:
q = 10;
for (int i=0;i<q;i++) {
q = 20;
// Do some stuff
}
Luego, el ciclo itera 20 veces, porque q se cambió a 20 en el momento en que crecí lo suficiente como para que importara.
La forma C es más flexible, por supuesto. Sin embargo, esto tiene bastantes implicaciones negativas. Lo obvio es que el programa debe perder el esfuerzo al volver a verificar la condición de bucle en cada ciclo. Un buen optimizador podría ser lo suficientemente inteligente como para evitar el problema en casos simples como este, pero ¿qué sucede si "q" es global y "hacer algunas cosas" incluye una llamada a procedimiento (y en teoría podría modificar q)?
La realidad es que sabemos mucho más sobre el bucle de Ada que sobre el bucle C. Eso significa que con el mismo nivel de inteligencia y esfuerzo en su optimizador, Ada puede hacer un mejor trabajo de optimización. Por ejemplo, el compilador de Ada sabe que puede reemplazar todo el ciclo con 10 copias de los contenidos, sin importar cuáles sean. El optimizador de CA debería examinar y analizar los contenidos.
En realidad, esta es solo una de las muchas formas en que el diseño de la sintaxis C bloquea el compilador.
En x86, para el bucle se puede hacer en el nivel de ensamblaje sin hacer un ciclo while. La instrucción de bucle disminuye el valor del registro ecx y salta al operando si ecx es mayor que 0, de lo contrario no hace nada. Este es un clásico para loop.
mov ecx, 0x00000010h
loop_start:
;loop body
loop loop_start
;end of loop
Lo que estos puristas del lenguaje nunca parecen darse cuenta es el objetivo de C, y hasta cierto punto C ++, le está dando la posibilidad de implementar lo que quiere y cómo lo quiere. Ahora bien, si el resultado de su expresión final cambia, sería mejor usar un ciclo while. Tal vez el problema es que los programadores tienen la impresión de que C implementa un "alto nivel", pero todos deberían saber que C es un lenguaje de bajo nivel.
Loop desenrollar tal vez? Si sabe cuántas veces se va a ejecutar el bucle for, puede copiar y pegar literalmente el contenido del bucle. La mayoría de los ciclos while se basarán en alguna condición que no sea un conteo simple de 0 a N, por lo que no podrán usar esta optimización.
Toma este ejemplo
int x;
for (x = 10; x != 0; --x)
{
printf ("Hello/n");
}
Sé que normalmente harías x = 0; x <= 10; ++x
x = 0; x <= 10; ++x
x = 0; x <= 10; ++x
, pero todo se revelará en el ensamblaje.
algún pseudo ensamblado:
mov 10, eax
loop:
print "hello"
dec eax
jne loop
En este ejemplo, seguimos retrocediendo alrededor del ciclo para imprimir "hola" 10 veces. Sin embargo, estamos evaluando la condición con la instrucción jne
cada vez que jne
el ciclo.
Si lo desenrollamos podríamos simplemente poner
print "hello"
print "hello"
print "hello"
print "hello"
print "hello"
print "hello"
print "hello"
print "hello"
print "hello"
print "hello"
No necesitaríamos ninguna de las otras instrucciones, por lo que es más rápido. Este es solo un ejemplo simple - ¡Trataré de encontrar uno mejor!
Magnus tiene razón, pero también debe tenerse en cuenta que en la mayoría de los idiomas (pre-C), el condicional es el criterio final (es decir, "detener cuando iguale 100"). En C (y en la mayoría de los idiomas posteriores a C), es el criterio de continuación (es decir, "continuar mientras yo sea menor de 100").
Probablemente se refiere a bucles como for i:=0 to N
y para cada bucle que itera sobre los elementos de un conjunto. Sospecho que todos los lenguajes que tienen un estilo C para bucle realmente lo obtuvieron de C.
Si todo lo que quieres es un simple ciclo de conteo, entonces
for (i=0; i<100; i++) dostuff();
estará bien, y el compilador puede optimizarlo.
Si usa una función en la parte de continuación de la declaración for, como
for (i=0; i<strlen(s); i++) dostuff();
a continuación, la función se evaluará cada vez y esto no suele ser una buena idea, ya que los gastos generales de la función ralentizarán su proceso. A veces puede ralentizar su proceso hasta el punto de la inutilizabilidad.
Si el valor de retorno de la función no cambiará durante la iteración, extráigalo del ciclo:
slen = strlen(s);
for (i=0; i<slen; i++) dostuff();
Pero hay ocasiones en que la función devolverá valores diferentes en cada llamada, y luego no desea que se extraiga del ciclo:
for (isread(fd, &buffer, ISFIRST);
isstat(fd) >= 0;
isread(fd, &buffer, ISNEXT)
{
dostuff(buffer);
}
y quieres que se evalúe cada vez. (Ese es un ejemplo ligeramente artificial basado en el trabajo que hago, pero muestra el potencial).
C te da la habilidad cruda de hacer rodar tu bucle de cualquier forma que puedas. Debe saber cómo se supone que funciona su bucle y optimizarlo lo mejor que pueda, según sus necesidades.
Ese último ejemplo podría haberse expresado como un ciclo while:
isread(fd, &buffer, ISFIRST);
while (isstat(fd) >= 0)
{
dostuff(buffer);
isread(fd, &buffer, ISNEXT);
}
pero no es tan limpio, y si uso un continue en el bucle, entonces tengo que llamar al iterador isread nuevamente. Poner todo en un bucle for lo hace más nítido y garantiza que el iterar isread se llame en cada ciclo.
Escribo funciones de nivel inferior para que puedan usarse en bucles de este tipo. Reúne todos los elementos del ciclo while para que pueda entenderlo más fácilmente.
Tal vez Knuth se está refiriendo a BASIC.
En BASIC (el más antiguo, no sé sobre VB), los bucles FOR se implementaron de manera diferente.
Por ejemplo, para hacer un bucle diez veces
PARA N = 1 A 10
...
SIGUIENTE N
---- O ---- para encontrar la suma de números pares por debajo de 100
PARA N = 0 A 100 PASO 2
...
SIGUIENTE N
En C, puede tener cualquier condición en "para" para verificar si sale del ciclo. Más flexible, creo.
La misma ''practicidad'' significa que el aspirante a CS debe aprender los errores de Kernighan al diseñar C, especialmente el infame hecho de que un bucle for evalúa repetidamente la condición para, lo que duplica y no coincide con el comportamiento de la mayoría de los otros idiomas que implementar un bucle for.
Muaahahahaa!
Me gustan las afirmaciones básicas (juego de palabras) del crítico:
- Los errores de Kernighan
- hecho infame
- no coincide con el comportamiento de la mayoría de los otros idiomas
Para mí, huele a alguien que nunca logró dominar las características básicas / filosofía de C.
Introducción
Como todavía estudiaba física en la universidad, encontré que C (es decir, los lenguajes similares a C) se convertiría en mi idioma de elección cuando descubriera C''s for loop.
Entonces, aparentemente, la falla de estilo de uno es el éxito del estilo de otra persona. Esto pondrá fin a la parte subjetiva de esta discusión.
¿Quizás Kernigan debería haber sido llamado loop?
¿Es una broma? Talvez no.
El autor de la revisión aparentemente decidió que cada construcción de lenguaje debería tener el mismo comportamiento en todos los idiomas. Por lo tanto, renombrarlo como bucle habría aliviado su incomodidad.
El problema es que el autor no comprende que C es una extensión de tiempo , no un ciclo diferente. Esto significa que es una versión sintáctica de la luz de azúcar de for , en lugar del tiempo, mientras que otros lenguajes pueden haber sido castrados.
Es realmente necesario C?
Citando al autor de la revisión, él / ella hace las siguientes afirmaciones sobre por y mientras :
- for es para bucles constantes, de principio a fin
- mientras que para bucles se evalúa una condición en cada iteración
Tener características ortogonales es algo bueno cuando puedes combinarlas. Pero en todos los idiomas que conozco, no hay forma de combinar ambos por un tiempo .
Ejemplo: ¿Qué sucede si, para el lenguaje del revisor, necesita un ciclo que va de principio a fin (como a para ), pero que aún puede evaluar en cada iteración de ciclo (como un tiempo ): podría necesitar encontrar en un subconjunto de un contenedor (no el contenedor completo), un valor de acuerdo con alguna prueba con estado?
En un lenguaje tipo C, usa el para . En el lenguaje ideal del revisor, usted simplemente, bueno, piratea un código feo, llorando porque no tiene un ciclo for_while (o C para el ciclo).
Conclusión
Creo que podemos resumir la crítica del crítico de C (y de los lenguajes tipo C) con la siguiente afirmación " El infame error de Kernigan de no darle a C la sintaxis de los idiomas existentes anteriores ", que se puede resumir nuevamente como " Nunca pienses diferente " .
Citando a Bjarne Stroustrup:
Solo hay dos tipos de idiomas: aquellos de los que la gente se queja y los que nadie usa.
Entonces, en general, el comentario del crítico debe ser considerado como un elogio.
^ _ ^