Descargo de responsabilidad : conozco perfectamente la semántica del incremento de prefijo y postfijo. Así que por favor no me expliquen cómo funcionan.
Al leer las preguntas sobre el desbordamiento de la pila, no puedo evitar notar que los programadores se confunden con el operador de incremento de postfix una y otra vez. De esto surge la siguiente pregunta: ¿hay algún caso de uso en el que el incremento de postfix proporcione un beneficio real en términos de calidad de código?
Permítanme aclarar mi pregunta con un ejemplo. Aquí hay una implementación súper concisa de strcpy
:
while (*dst++ = *src++);
Pero ese no es exactamente el código más autodocumentado en mi libro (y produce dos advertencias molestas en compiladores sanos). Entonces, ¿qué hay de malo con la siguiente alternativa?
while (*dst = *src)
{
++src;
++dst;
}
Entonces podemos deshacernos de la asignación confusa en la condición y obtener un código completamente libre de advertencia:
while (*src != '\0')
{
*dst = *src;
++src;
++dst;
}
*dst = '\0';
(Sí, lo sé, src
y dst
tendré diferentes valores finales en estas soluciones alternativas, pero dado que strcpy
regresa inmediatamente después del ciclo, no importa en este caso).
Parece que el propósito del incremento de postfix es hacer que el código sea lo más breve posible. Simplemente no veo cómo esto es algo por lo que debemos luchar. Si esto fue originalmente sobre rendimiento, ¿sigue siendo relevante hoy?
strcpy
método de esta manera (por razones que ya ha mencionado).int c = 0; c++;
Respuestas:
Si bien alguna vez tuvo algunas implicaciones de rendimiento, creo que la verdadera razón es para expresar su intención limpiamente. La verdadera pregunta es si algo
while (*d++=*s++);
expresa la intención claramente o no. OMI, lo hace, y creo que las alternativas que ofrece son menos claras, pero eso puede (fácilmente) ser el resultado de haber pasado décadas acostumbrándose a cómo se hacen las cosas. Después de haber aprendido C de K & R (porque no había casi nada de otros libros sobre C en el momento) probablemente también ayuda.Hasta cierto punto, es cierto que la brevedad se valoró en un grado mucho mayor en el código anterior. Personalmente, creo que esto fue en gran medida algo bueno: comprender algunas líneas de código suele ser bastante trivial; lo que es difícil es comprender grandes porciones de código. Pruebas y estudios han demostrado repetidamente que ajustar todo el código en la pantalla a la vez es un factor importante para comprender el código. A medida que las pantallas se expanden, esto parece seguir siendo cierto, por lo que mantener el código (razonablemente) breve sigue siendo valioso.
Por supuesto, es posible ir por la borda, pero no creo que sea así. Específicamente, creo que se va por la borda cuando la comprensión de una sola línea de código se vuelve extremadamente difícil o lleva mucho tiempo, específicamente, cuando comprender menos líneas de código consume más esfuerzo que comprender más líneas. Eso es frecuente en Lisp y APL, pero no parece (al menos para mí) el caso aquí.
Estoy menos preocupado por las advertencias del compilador: según mi experiencia, muchos compiladores emiten advertencias completamente ridículas de forma bastante regular. Si bien creo que la gente debería entender su código (y cualquier advertencia que pueda producir), un código decente que active una advertencia en algún compilador no es necesariamente incorrecto. Es cierto que los principiantes no siempre saben lo que pueden ignorar con seguridad, pero no somos principiantes para siempre, y tampoco necesitamos codificar como nosotros.
fuente
if(x = y)
, lo juro!), Debe ajustar las opciones de advertencia de su compilador para que no se pierda accidentalmente advertencias que te gustan.-Wno-X
, pero si solo quiere hacer una excepción y desea que la advertencia se aplique en cualquier otro lugar, esto es mucho más comprensible que usar saltos arcanos como Chris se refiere .(void)x
advertencias no silenciadas son expresiones idiomáticas bastante conocidas para silenciar advertencias útiles. La anotación se parece un pocopragmas
, no es portátil en el mejor de los casos: /Es, err, era, una cosa de hardware
Interesante que lo notaras. El incremento de postfix probablemente esté allí por varias razones perfectamente buenas, pero como muchas cosas en C, su popularidad se puede rastrear hasta el origen del lenguaje.
Aunque C se desarrolló en una variedad de máquinas tempranas y con poca potencia, C y Unix alcanzaron el gran éxito relativo con los modelos gestionados por memoria del PDP-11. Estas eran computadoras relativamente importantes en su día y Unix era mucho mejor, exponencialmente mejor, que los otros 7 sistemas operativos malos disponibles para el -11.
Y resulta que en el PDP-11,
y
... se implementaron en hardware como modos de direccionamiento. (Además
*p
, pero no hay otra combinación). En esas primeras máquinas, todas menos de 0.001 GHz, guardar una o dos instrucciones en un bucle debe haber sido casi una espera de un segundo o una espera de un minuto o una salida -lunch diferencia. Esto no se refiere precisamente al postincremento específicamente, pero un bucle con puntero postincremento podría haber sido mucho mejor que indexar en ese momento.Como consecuencia, los patrones de diseño son modismos C que se convirtieron en macros C mentales.
Es algo así como declarar variables justo después de un
{
... no desde que C89 era actual ha sido un requisito, pero ahora es un patrón de código.Actualización: Obviamente, la razón principal
*p++
está en el lenguaje es porque es exactamente lo que a menudo se quiere hacer. La popularidad del patrón de código se vio reforzada por el hardware popular que apareció y coincidió con el patrón ya existente de un lenguaje diseñado ligeramente antes de la llegada del PDP-11.En estos días no hace ninguna diferencia qué patrón se utiliza, o si utiliza la indexación, y por lo general el programa a un nivel más alto de todos modos, pero debe haber importado mucho en esas máquinas 0.001GHz, y el uso que no sea nada
*--x
o*x++
te habría significado no "consiguió" el PDP-11, y es posible que haya personas que se le acerquen y le digan "¿sabía eso ..." :-) :-)fuente
−−i
yi++
en C. Sii
yj
ambas eran variables de registro, una expresión que*(−−i) = *(j++)
podría compilarse en una sola instrucción de máquina. [...] Dennis Ritchie contradice inequívocamente este mito popular. [El desarrollo del lenguaje C ] "El prefijo y el postfix
--
y los++
operadores fueron introducidos en el lenguaje B (predecesor de C) por Ken Thompson, y no, no se inspiraron en el PDP-11, que no existía en ese momento.Cita de "El desarrollo del lenguaje C" de Dennis Ritchie :
fuente
La razón obvia para que exista el operador postincremento es para que no tenga que escribir expresiones como
(++x,x-1)
o por(x+=1,x-1)
todas partes o separar declaraciones triviales inútiles con efectos secundarios fáciles de entender en múltiples declaraciones. Aquí hay unos ejemplos:Siempre que leer o escribir una cadena en la dirección hacia adelante, el postincremento, y no el preincremento, es casi siempre la operación natural. Casi nunca he encontrado usos en el mundo real para el preincremento, y el único uso importante que he encontrado para el precremento es convertir números en cadenas (porque los sistemas de escritura humanos escriben números al revés).
Editar: en realidad no sería tan feo; en lugar de
(++x,x-1)
usted, por supuesto, podría usar++x-1
o(x+=1)-1
. Todavíax++
es más legible.fuente
(++x,x-1)
(¿llamada de función, tal vez?)x++
en la mayoría de los casos (una excepción:x
es un tipo sin signo con un rango inferiorint
y el valor inicial dex
es el valor máximo de ese tipo).El PDP-11 ofreció operaciones posteriores al incremento y al decremento previo en el conjunto de instrucciones. Ahora estas no eran instrucciones. Eran modificadores de instrucciones que le permitían especificar que deseaba el valor de un registro antes de que se incrementara o después de que se redujera.
Aquí hay un paso de copia de palabras en el lenguaje de máquina:
que movía una palabra de un lugar a otro, señalada por los registros, y los registros estarían listos para hacerlo nuevamente.
A medida que C se construyó sobre y para el PDP-11, muchos conceptos útiles se introdujeron en C. C tenía la intención de ser un reemplazo útil para el lenguaje ensamblador. Pre-incremento y post-decremento fueron añadidos por simetría.
fuente
Dudo que alguna vez fuera realmente necesario. Hasta donde yo sé, no se compila en nada más compacto en la mayoría de las plataformas que usar el pre-incremento como lo hiciste, en el bucle. Era solo que en el momento en que se hizo, la brevedad del código era más importante que la claridad del código.
Eso no quiere decir que la claridad del código no sea (o no sea) importante, pero cuando estaba escribiendo en un módem de baudios bajos (todo lo que mide en baudios es lento) donde cada pulsación de tecla tenía que llegar a el mainframe y luego se repite un byte a la vez con un solo bit utilizado como verificación de paridad, no quería tener que escribir mucho.
Esto es algo así como el & y | operador que tiene menor prioridad que ==
Fue diseñado con las mejores intenciones (una versión sin cortocircuito de && y ||), pero ahora confunde a los programadores todos los días y probablemente nunca cambiará.
De todos modos, esta es solo una respuesta, ya que creo que no hay una buena respuesta a su pregunta, pero es probable que un codificador de gurú más que yo me demuestre que estoy equivocado.
--EDITAR--
Debo señalar que yo encuentro que tiene tanto increíblemente útil, sólo estoy señalando que no va a cambiar si alguien nos guste o no.
fuente
Me gusta el aperador de postfix cuando trato con punteros (ya sea que los esté desreferenciando o no) porque
se lee más naturalmente como "pasar al siguiente lugar" que el equivalente
lo que seguramente debe confundir a los principiantes la primera vez que lo ven usado con un puntero donde sizeof (* p)! = 1.
¿Estás seguro de que estás indicando el problema de confusión correctamente? No es lo que confunde a los principiantes el hecho de que el operador postfix ++ tiene mayor prioridad que el operador de desreferencia * para que
analiza como
y no
como algunos podrían esperar?
(¿Crees que eso es malo? Ver Lectura de declaraciones C ).
fuente
Entre los elementos sutiles de una buena programación están la localización y el minimalismo :
En el mismo espíritu,
x++
se puede ver como una forma de localizar una referencia al valor actual de al mismox
tiempo que indica de inmediato que, al menos por ahora, ha terminado con ese valor y desea pasar al siguiente (válido six
es unint
o un puntero o iterador). Con un poco de imaginación, podría comparar esto con dejar que el antiguo valor / posición dex
"fuera de alcance" inmediatamente después de que ya no sea necesario, y pasar al nuevo valor. El punto exacto donde es posible esa transición se resalta con el postfix++
. La implicación de que este es probablemente el uso final del "viejo"x
puede ser una información valiosa sobre el algoritmo, ayudando al programador a medida que escanea el código circundante. Por supuesto, el postfix++
puede poner al programador a la búsqueda de usos del nuevo valor, lo que puede o no ser bueno dependiendo de cuándo se necesita realmente ese nuevo valor, por lo que es un aspecto del "arte" o "artesanía" de la programación para determinar qué es más útil en las circunstancias.Si bien muchos principiantes pueden estar confundidos por una característica, vale la pena equilibrar eso con los beneficios a largo plazo: los principiantes no se quedan como principiantes por mucho tiempo.
fuente
Wow, muchas respuestas no muy acertadas (si puedo ser tan audaz), y por favor perdóname si estoy señalando lo obvio, particularmente a la luz de tu comentario para no señalar la semántica, sino lo obvio (desde la perspectiva de Stroustrup, supongo) ¡todavía no parece haber sido publicado! :)
Postfix
x++
produce un temporal que se pasa 'hacia arriba' en la expresión, mientras que la variablex
se incrementa posteriormente.El prefijo
++x
no produce un objeto temporal, pero incrementa 'x' y pasa el resultado a la expresión.El valor es la conveniencia, para quienes conocen al operador.
Por ejemplo:
Por supuesto, los resultados de este ejemplo se pueden producir a partir de otro código equivalente (y quizás menos oblicuo).
Entonces, ¿por qué tenemos el operador postfix? Supongo que porque es un idioma que persiste, a pesar de la confusión que obviamente crea. Es una construcción heredada que alguna vez se percibió que tenía valor, aunque no estoy seguro de que el valor percibido fuera tanto por el rendimiento como por la conveniencia. Esa conveniencia no se ha perdido, pero creo que ha aumentado la apreciación de la legibilidad, lo que resulta en un cuestionamiento del propósito del operador.
¿Necesitamos más el operador postfix? Probablemente no. Su resultado es confuso y crea una barrera para la comprensión. Algunos buenos programadores sabrán de inmediato dónde encontrarlo útil, a menudo en lugares donde tiene una belleza peculiar "PERL-esque". El costo de esa belleza es la legibilidad.
Estoy de acuerdo en que el código explícito tiene beneficios sobre la brevedad, la legibilidad, la comprensión y la facilidad de mantenimiento. Los buenos diseñadores y gerentes quieren eso.
Sin embargo, hay una cierta belleza que algunos programadores reconocen que los alienta a producir código con cosas puramente para una simplicidad hermosa, como el operador de posfijo, en qué código no hubieran pensado de otra manera, o lo hubieran encontrado intelectualmente estimulante. Hay algo en eso que lo hace más hermoso, si no más deseable, a pesar de la concisión.
En otras palabras, algunas personas encuentran
while (*dst++ = *src++);
simplemente una solución más hermosa, algo que te deja sin aliento con su simplicidad, tanto como si fuera un pincel para el lienzo. Que te veas obligado a entender el idioma para apreciar la belleza solo se suma a su magnificencia.fuente
for
declaración, diablos, inclusowhile
(tenemosgoto
, después de todo)?Veamos la justificación original de Kernighan & Ritchie (K&R original, páginas 42 y 43):
El texto continúa con algunos ejemplos que usan incrementos dentro del índice, con el objetivo explícito de escribir código " más compacto ". Entonces, la razón detrás de estos operadores es la conveniencia de un código más compacto.
Los tres ejemplos dados (
squeeze()
,getline()
ystrcat()
) usan solo postfix dentro de expresiones usando indexación. Los autores comparan el código con una versión más larga que no utiliza incrementos incrustados. Esto confirma que el foco está en la compacidad.K&R resalta en la página 102, el uso de estos operadores en combinación con la desreferenciación del puntero (por ejemplo,
*--p
y*p--
). No se dan más ejemplos, pero nuevamente, dejan en claro que el beneficio es la compacidad.El prefijo se usa, por ejemplo, muy comúnmente cuando se decrementa un índice o un puntero desde el final.
fuente
Voy a estar en desacuerdo con la premisa de que
++p
, dep++
alguna manera, es difícil de leer o no está claro. Uno significa "incrementar py luego leer p", el otro significa "leer py luego incrementar p". En ambos casos, el operador en el código está exactamente donde está en la explicación del código, así que si sabe lo que++
significa, sabrá lo que significa el código resultante.Puede crear código ofuscado usando cualquier sintaxis, pero no veo un caso aquí que
p++
/++p
es inherentemente obfuscatory.fuente
Esto es del POV de un ingeniero eléctrico:
Hay muchos procesadores que tienen operadores incorporados de incremento posterior y decremento previo con el fin de mantener una pila de último en entrar, primero en salir (LIFO).
podría ser como:
No sé, pero esa es la razón por la que esperaría ver los operadores de incremento de prefijo y postfijo.
fuente
Para subrayar el punto de Christophe sobre la compacidad, quiero mostrarles algunos códigos de V6. Esto es parte del compilador de C original, de http://minnie.tuhs.org/cgi-bin/utree.pl?file=V6/usr/source/c/c00.c :
Este es un código que se pasó en samizdat como una educación de buen estilo, y sin embargo, nunca lo escribiríamos así hoy. Mire cuántos efectos secundarios se han incluido
if
y laswhile
condiciones, y por el contrario, cómo ambosfor
bucles no tienen una expresión de incremento porque eso se hace en el cuerpo del bucle. Observe cómo los bloques solo se envuelven entre llaves cuando es absolutamente necesario.Parte de esto fue ciertamente una microoptimización manual del tipo que esperamos que haga el compilador para nosotros hoy, pero también plantearía la hipótesis de que cuando toda su interfaz con la computadora es un solo cristal de 80x25, quiere su código ser lo más denso posible para que pueda ver más al mismo tiempo.
(El uso de buffers globales para todo es probablemente una resaca de cortar los dientes en lenguaje ensamblador).
fuente
rp->hflag =| xdflg
, lo que creo que será un error de sintaxis en un compilador moderno. "En algún momento" es precisamente V6, como sucede; El cambio para+=
formar happend en V7. (El compilador V6 solo acepta=+
, si estoy leyendo este código correctamente, vea el símbolo () en el mismo archivo).Quizás deberías pensar en cosméticos también.
podría decirse que "se ve mejor" que
Por alguna razón, el operador posterior al incremento parece muy atractivo. Es corto, dulce y aumenta todo en 1. Compárelo con el prefijo:
"mira hacia atrás" ¿no? Nunca ves un signo más PRIMERO en matemáticas, excepto cuando alguien está siendo tonto al especificar algo positivo (
+0
o algo así). Estamos acostumbrados a ver OBJETO + ALGO¿Tiene algo que ver con la calificación? A, A +, A ++? ¡Puedo decirte que al principio estaba bastante molesto porque la gente de Python se había eliminado
operator++
de su lenguaje!fuente
i++
devuelve el valor original dei
yi+=1
y++i
devolver el valor original dei
más 1. Dado que está utilizando los valores de retorno de flujo de control, esto es importante.Contando hacia atrás 9 a 0 inclusive:
O el bucle while equivalente.
fuente
for (unsigned i = 9; i <= 9; --i)
?