¿Por qué tenemos un incremento de postfix?

55

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é, srcy dsttendré diferentes valores finales en estas soluciones alternativas, pero dado que strcpyregresa 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?

flujo libre
fuente
77
La variante postfix es la más utilizada, por lo que si va a presentar un caso en su contra, es mejor que sea un caso bastante fuerte. Y su primer ejemplo es un poco tímido, ya que sospecho que pocas personas realmente codificarían el strcpymétodo de esta manera (por razones que ya ha mencionado).
Robert Harvey
44
¿Por qué tener cualquiera?
James McNellis 01 de
31
Si eliminamos todo del lenguaje que tiene el potencial de confundir a los programadores, no tendríamos muchas características. El hecho de que algo no sea útil para usted, o que rara vez sea útil, no significa que deba ser cortado. Si ya no es relevante, no lo use; el fin.
Cody Gray
44
Sí, pero nunca se puede hacerint c = 0; c++;
Biff MaGriff
44
@Cody: Porque algunas cosas son confusas y útiles. No está confundido por el post-incremento en sí, está confundido acerca de su utilidad . No deberíamos tener cosas inútiles, confusas o no.
GManNickG 01 de

Respuestas:

23

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.

Jerry Coffin
fuente
12
+1 por señalar que la versión concisa es más fácil de leer para algunos de nosotros. :-)
1
Si no le gustan algunas de las advertencias de su compilador (y hay algunas de las que podría prescindir, en serio, ¡tenía la intención de escribir if(x = y), lo juro!), Debe ajustar las opciones de advertencia de su compilador para que no se pierda accidentalmente advertencias que te gustan.
44
@Brooks Moses: Estoy mucho menos entusiasmado con eso. No me gusta contaminar mi código para compensar las deficiencias del compilador.
Jerry Coffin
1
@Jerry, @Chris: esta anotación está destinada al caso en el que crees que la advertencia suele ser algo bueno , pero no quieres que se aplique a una línea en particular que está haciendo algo inusual. Si nunca desea la advertencia y la considera una deficiencia, úsela -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 .
1
@Brooks: los paréntesis dobles y las (void)xadvertencias no silenciadas son expresiones idiomáticas bastante conocidas para silenciar advertencias útiles. La anotación se parece un poco pragmas, no es portátil en el mejor de los casos: /
Matthieu M.
32

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,

*--p

y

*p++

... 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 *--xo *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 ..." :-) :-)

DigitalRoss
fuente
25
Wikipedia dice: "Un mito popular (falso) es que la arquitectura del conjunto de instrucciones del PDP-11 influyó en el uso idiomático del lenguaje de programación C. Los modos de direccionamiento de incremento y decremento del PDP-11 corresponden a las construcciones −−iy i++en C. Si iy jambas 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 ] "
fredoverflow
3
Huh (del enlace provisto en el comentario de FredOverflow): "La gente a menudo adivina que fueron creados para usar los modos de dirección de incremento automático y decremento automático proporcionados por el DEC PDP-11 en el que C y Unix se hicieron populares por primera vez. Esto es históricamente imposible, ya que no había PDP-11 cuando se desarrolló B. El PDP-7, sin embargo, tenía algunas celdas de memoria de 'auto-incremento', con la propiedad de que una referencia indirecta de memoria a través de ellas incrementaba la celda. Esta característica probablemente sugirió tales operadores a Thompson; la generalización para hacerlos prefijos y postfijos era suya ".
3
DMR solo dijo que el PDP-11 no era la razón por la que se agregó a C. Yo también dije eso. Lo que dije fue que se utilizó con ventaja en el PDP-11 y se convirtió en un patrón de diseño popular por esa razón exacta. Si Wikipedia contradice eso , simplemente están equivocados, pero no lo leo de esa manera. Está mal redactado en esa página W.
DigitalRoss
3
@FredOverflow. Creo que malinterpretaste exactamente qué es el "mito popular". El mito es que C fue diseñado para explotar esas 11 operaciones, no es que no existan y que el patrón de código no se hizo popular porque todos sabían que se mapearon bien al 11. En caso de que no esté claro: el PDP-11 realmente implementa esos dos modos en hardware para casi todas las instrucciones como modo de direccionamiento, y realmente fue la primera máquina importante en la que se ejecutó C.
DigitalRoss
2
"debe haber importado mucho en esas máquinas de 0.001GHz". Um, odio decirlo porque data de mí, pero tenía manuales de Motorola e Intel para que mis CPU buscaran el tamaño de las instrucciones y cuántos ciclos tomaron. Encontrar los códigos más pequeños sumados a la posibilidad de agregar una función más a una cola de impresión y guardar un par de ciclos significaba que un puerto serie podría mantenerse al día con algún protocolo como ese MIDI recién inventado. C alivió parte de la selección, especialmente a medida que los compiladores mejoraron, pero aún así era importante saber cuál era la forma más eficiente de escribir código.
The Tin Man
14

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 :

Thompson dio un paso más al inventar el ++y--operadores, que aumentan o disminuyen; su posición de prefijo o postfijo determina si la alteración ocurre antes o después de observar el valor del operando. No estaban en las primeras versiones de B, pero aparecieron en el camino. La gente a menudo adivina que fueron creados para usar los modos de dirección de incremento automático y decremento automático proporcionados por el DEC PDP-11 en el que C y Unix se hicieron populares por primera vez. Esto es históricamente imposible, ya que no había PDP-11 cuando se desarrolló B. Sin embargo, el PDP-7 tenía algunas celdas de memoria de 'auto-incremento', con la propiedad de que una referencia indirecta de memoria a través de ellas incrementaba la celda. Esta característica probablemente sugirió tales operadores a Thompson; la generalización para hacerlos ambos prefijo y postfix era suya. En efecto,++xera más pequeño que el de x=x+1.

Keith Thompson
fuente
8
No, no estoy relacionado con Ken Thompson.
Keith Thompson
Gracias por esta referencia muy interesante! Esto confirma mi cita de K&R original que sugirió el objetivo de compacidad en lugar de una optimización técnica. Sin embargo, no sabía que estos operadores ya existían en B.
Christophe
@BenPen Hasta donde yo sé, no existe una política de cierre de preguntas si hay un duplicado en un sitio de SE diferente , siempre que ambas preguntas se ajusten a sus respectivos sitios.
Ordous
Interesante ... El Programador no es una pregunta tan cercana.
BenPen
@BenPen: La respuesta aceptada a la pregunta de Stack Overflow ya cita la misma fuente que yo (aunque no tanto).
Keith Thompson
6

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:

while (*s) x+=*s++-'0';

if (x) *s++='.';

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-1o (x+=1)-1. Todavía x++es más legible.

R ..
fuente
Debe tener cuidado con esas expresiones ya que está modificando y leyendo una variable entre dos puntos de secuencia. El orden en que se evalúan esas expresiones no está garantizado.
@Snowman: ¿Cuál? No veo tales problemas en mi respuesta.
R ..
si tienes algo como (++x,x-1)(¿llamada de función, tal vez?)
@Snowman: Esa no es una llamada de función. Es el operador de coma C, que es un punto de secuencia, entre paréntesis para controlar la precedencia. El resultado es el mismo que x++en la mayoría de los casos (una excepción: xes un tipo sin signo con un rango inferior inty el valor inicial de xes el valor máximo de ese tipo).
R ..
Ah, el operador de coma complicado ... cuando una coma no es una coma. El paréntesis y la rareza del operador de coma me desconcertaron. ¡No importa!
4

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:

movw (r1)++,(r2)++

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.

BobDalgleish
fuente
Eso es modificadores brillantes ... Me dan ganas de aprender el ensamblaje PDP-11. Por cierto, ¿tiene una referencia sobre "para la simetría?" Tiene sentido, pero esto es historia, a veces tener sentido no era la clave. LOL
BenPen
2
También conocido como modos de direccionamiento . El PDP-11 proporcionó un incremento posterior y un decremento previo que se combinaron muy bien para las operaciones de apilamiento.
Erik Eidt
1
El PDP-8 proporcionó una indirección de memoria posterior al incremento, pero ninguna operación de decremento previo correspondiente. Con la memoria de núcleo magnético, la lectura de una palabra de memoria la borró (!), Por lo que el procesador utilizó una función de reescritura para restaurar la palabra que acaba de leer. La aplicación de un incremento antes de la reescritura se produjo de forma bastante natural, y se hizo posible un movimiento de bloque más eficiente.
Erik Eidt
3
Lo sentimos, el PDP-11 no inspiró a estos operadores. Todavía no existía cuando Ken Thompson los inventó. Mira mi respuesta .
Keith Thompson
3

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
Esto ni siquiera es remotamente cierto. En ningún momento la "brevedad del código" fue más importante que la claridad.
Cody Gray
Bueno, yo diría que fue mucho más un punto de enfoque. Sin embargo, tienes toda la razón, ciertamente nunca ha sido más importante que la claridad del código ... para la mayoría de las personas.
66
Bueno, la definición de "conciso" es "suavemente elegante: pulido" o "usando pocas palabras: desprovisto de superfluidad", los cuales generalmente son algo bueno al escribir código. "Conciso" no significa "oscuro, difícil de leer y ofuscado" como mucha gente piensa.
James McNellis 01 de
@James: Si bien tienes razón, creo que la mayoría de las personas se refieren a la segunda definición de "conciso" cuando se usa en un contexto de programación: "usar pocas palabras; sin superfluidad". Según esa definición, es ciertamente más fácil imaginar situaciones en las que existe un compromiso entre la brevedad y la expresividad de un código en particular. Obviamente, lo correcto al escribir código es lo mismo que cuando se habla: hacer las cosas lo más cortas posible, pero no más cortas. Valorar la brevedad sobre la expresividad puede conducir a un código de aspecto ofuscado, pero ciertamente no debería.
Cody Gray
1
rebobina 40 años e imagina que tienes que ajustar tu programa en ese tambor que contendrá solo alrededor de 1000 caracteres, o escribir un compilador solo puede funcionar con 4 mil bytes de ram. Hubo momentos en que la terquedad versus la claridad ni siquiera eran un problema. Tenías que ser breve, no existía una computadora en este planeta que pudiera almacenar, ejecutar o compilar tu programa no específico.
nos
3

Me gusta el aperador de postfix cuando trato con punteros (ya sea que los esté desreferenciando o no) porque

p++

se lee más naturalmente como "pasar al siguiente lugar" que el equivalente

p += 1

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

*p++

analiza como

*(p++)

y no

(*p)++

como algunos podrían esperar?

(¿Crees que eso es malo? Ver Lectura de declaraciones C ).

Eric Giguere
fuente
2

Entre los elementos sutiles de una buena programación están la localización y el minimalismo :

  • poner variables en un alcance mínimo de uso
  • usando const cuando no se requiere acceso de escritura
  • etc.

En el mismo espíritu, x++se puede ver como una forma de localizar una referencia al valor actual de al mismo xtiempo que indica de inmediato que, al menos por ahora, ha terminado con ese valor y desea pasar al siguiente (válido si xes un into un puntero o iterador). Con un poco de imaginación, podría comparar esto con dejar que el antiguo valor / posición de x"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" xpuede 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.

Tony
fuente
2

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 variable xse incrementa posteriormente.

El prefijo ++xno 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:

int x = 1;
foo(x++); // == foo(1)
// x == 2: true

x = 1;
foo(++x); // == foo(2)
// x == 2: true

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.

Brian M. Hunt
fuente
3
+1 "algunas personas encuentran while (* dst ++ = * src ++); simplemente para ser una solución más hermosa". Seguro. Las personas que no entienden el lenguaje ensamblador realmente no entienden cuán elegante puede ser C, pero esa declaración de código en particular es una estrella brillante.
The Tin Man
"¿Necesitamos más el operador de postfix? Probablemente no". - No puedo evitar pensar en las máquinas de Turing aquí. ¿Alguna vez hemos necesitado variables automáticas, la fordeclaración, diablos, incluso while(tenemos goto, después de todo)?
@Bernd: ¡Ja! ¡Imagina cierres! Cierres! ¡Escandaloso! jajaja
Brian M. Hunt
Cierres! Que ridículo. ¡Solo dame una cinta! En serio, lo que quise decir es que no creo / necesito / una característica debería ser el criterio de decisión principal (a menos que desee diseñar, digamos, lisp). IME Uso (no, necesito ) los operadores de postfix casi exclusivamente, y solo rara vez necesito el prefijo uno.
2

Veamos la justificación original de Kernighan & Ritchie (K&R original, páginas 42 y 43):

Los aspectos inusuales son que ++ y - se pueden usar como prefijo o como postfix. (...) En el contexto donde no se desea ningún valor (..) elija el prefijo o el postfijo según el gusto. Pero hay situaciones en las que se requiere específicamente una u otra.

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()y strcat()) 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, *--py *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.

 int i=0; 
 while (i<10) 
     doit (i++);  // calls function for 0 to 9  
                  // i is 10 at this stage
 while (--i >=0) 
     doit (i);    // calls function from 9 to 0 
Christophe
fuente
1

Voy a estar en desacuerdo con la premisa de que ++p, de p++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++/ ++pes inherentemente obfuscatory.

filosodad
fuente
1

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:

 float stack[4096];
 int stack_pointer = 0;

 ... 

 #define push_stack(arg) stack[stack_pointer++] = arg;
 #define pop_stack(arg) arg = stack[--stack_pointer];

 ...

No sé, pero esa es la razón por la que esperaría ver los operadores de incremento de prefijo y postfijo.

robert bristow-johnson
fuente
1

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 :

/*
 * Look up the identifier in symbuf in the symbol table.
 * If it hashes to the same spot as a keyword, try the keyword table
 * first.  An initial "." is ignored in the hash.
 * Return is a ptr to the symbol table entry.
 */
lookup()
{
    int ihash;
    register struct hshtab *rp;
    register char *sp, *np;

    ihash = 0;
    sp = symbuf;
    if (*sp=='.')
        sp++;
    while (sp<symbuf+ncps)
        ihash =+ *sp++;
    rp = &hshtab[ihash%hshsiz];
    if (rp->hflag&FKEYW)
        if (findkw())
            return(KEYW);
    while (*(np = rp->name)) {
        for (sp=symbuf; sp<symbuf+ncps;)
            if (*np++ != *sp++)
                goto no;
        csym = rp;
        return(NAME);
    no:
        if (++rp >= &hshtab[hshsiz])
            rp = hshtab;
    }
    if(++hshused >= hshsiz) {
        error("Symbol table overflow");
        exit(1);
    }
    rp->hclass = 0;
    rp->htype = 0;
    rp->hoffset = 0;
    rp->dimp = 0;
    rp->hflag =| xdflg;
    sp = symbuf;
    for (np=rp->name; sp<symbuf+ncps;)
        *np++ = *sp++;
    csym = rp;
    return(NAME);
}

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 ify las whilecondiciones, y por el contrario, cómo ambos forbucles 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).

zwol
fuente
Manejar con cuidado: "ihash = + * sp ++;" En algún momento, C no solo tenía el operador + = sino también = +. Hoy, el = + es un operador de asignación, seguido de un plus unario que no hace nada, por lo que es equivalente a "ihash = * sp; sp + = 1;"
gnasher729
@ gnasher729 Sí, y más tarde sí 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).
zwol
-1

Quizás deberías pensar en cosméticos también.

while( i++ )

podría decirse que "se ve mejor" que

while( i+=1 )

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:

while( ++i )

"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 ( +0o 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!

bobobobo
fuente
1
Tus ejemplos no hacen lo mismo. i++devuelve el valor original de iy i+=1y ++idevolver el valor original de imás 1. Dado que está utilizando los valores de retorno de flujo de control, esto es importante.
David Thornley
Sí tienes razón. Pero solo estoy viendo la legibilidad aquí.
bobobobo
-2

Contando hacia atrás 9 a 0 inclusive:

for (unsigned int i=10; i-- > 0;)  {
    cout << i << " ";
}

O el bucle while equivalente.


fuente
1
¿Qué tal for (unsigned i = 9; i <= 9; --i)?
fredoverflow 01 de