Motivo del operador de incremento (post / pre) en Java o C #

8

Recientemente, me topé con preguntas sobre la predicción de la salida de código que usa en gran medida operadores de post / incremento previo en enteros. Soy un programador experimentado en C, así que me sentí como en casa, pero la gente hizo declaraciones de que Java solo copiaba ciegamente eso de C (o C ++), y que esta es una característica inútil en Java.

Dicho esto, no puedo encontrar una buena razón para eso (especialmente teniendo en cuenta la diferencia entre las formas post / pre) en Java (o C #), porque no es como si manipularas matrices y las usaras como cadenas en Java . Además, fue hace mucho tiempo cuando busqué por última vez bytecode, así que no sé si hay una INCoperación, pero no veo ninguna razón por la cual esto

for(int i = 0; i < n; i += 1)

podría ser menos efectivo que

for(int i = 0; i < n; i++)

¿Hay alguna parte particular del lenguaje donde esto sea realmente útil, o es solo una característica para traer programadores de C a la ciudad?

Nemanja Boric
fuente
2
"Gosling quería que el código escrito por alguien con experiencia en C (no en Java) fuera ejecutado por alguien acostumbrado a ejecutar PostScript en NeWS ..."
mosquito
77
@TimothyGroote No creo que sea una evaluación justa ya que 1) las matrices son mutables, las cadenas no lo son; 2) no puede asignar una Cadena a un char [] o viceversa. Ciertamente tienen forma de matriz, es probable que se implementen con matrices, y es bastante fácil convertir hacia y desde una matriz, pero definitivamente no son lo mismo.
Doval
44
Muchas buenas respuestas aquí, así que no volveré a decir lo que otros ya han dicho, excepto para enfatizar que estos operadores son características horribles. Son muy confusos; Después de más de 25 años, sigo confundiéndome antes y después de la semántica. Fomentan los malos hábitos como combinar la evaluación de resultados con la producción de efectos secundarios. Si estas características no hubieran estado en C / C ++ / Java / JavaScript / etc., no se habrían inventado para C #.
Eric Lippert
55
Para responder directamente a las reclamaciones hechas por su gente: C # no copió a ciegas nada de nadie. Cada característica de C # fue extremadamente cuidadosamente diseñada. Y estas no son características inútiles ; Tienen un uso.
Eric Lippert
2
@EricLippert: Creo (espero) la mayoría de nosotros de acuerdo en que la subasta pre / post es horrible, pero en ese contexto ... ¿por qué se incluyeron en C #? ¿Por el bien de la familiaridad?
Phoshi

Respuestas:

19

¿Es esto solo una característica para traer programadores de C a la ciudad?

Seguramente es una característica "traer programadores de C (o C ++) a la ciudad", pero usar la palabra "solo" aquí subestima el valor de semejante similitud.

  • hace que el código (o al menos fragmentos de código) sea más fácil de transferir de C (o C ++) a Java o C #

  • i++es menos de escribir que i += 1, y x[i++]=0;es mucho más idiomático quex[i]=0;i+=1;

Y sí, el código que utiliza en gran medida operadores de post / incremento previo puede ser difícil de leer y mantener, pero para cualquier código en el que estos operadores se usen incorrectamente, no esperaría un aumento drástico en la calidad del código, incluso cuando el lenguaje no proporcionaría estos operadores. Esto se debe a que si tiene desarrolladores que no se preocupan por la mantenibilidad, siempre encontrarán formas de escribir código difícil de entender.

Relacionado: ¿Hay alguna diferencia entre los operadores Java y C ++? A partir de esta respuesta, se puede deducir que cualquier comportamiento bien definido del operador en C es similar en Java, y Java solo agrega algunas definiciones para la precedencia del operador donde C tiene un comportamiento indefinido. Esto no parece una coincidencia, parece ser bastante intencional.

Doc Brown
fuente
10
"Si tienes desarrolladores que no se preocupan por el mantenimiento, siempre encontrarán formas de escribir código difícil de entender" - muy bien dicho
Lovis
¿Te suscribes a la creencia común entre los programadores de C de que solo tienes tantas pulsaciones de teclas C antes de morir? Guardar esa pulsación de tecla me parece una mala compensación.
Eric Lippert
@EricLippert: hola Eric, como uno de los creadores de C # ¿por qué no nos iluminas y nos cuentas un poco sobre tu motivación para mantener a los operadores en C # similares a Java y C?
Doc Brown
@DocBrown: su respuesta se aplica bastante bien a C #. Dejé un comentario arriba.
Eric Lippert
2
@DocBrown: x[i++]=0es idiomático en C, pero no estoy de acuerdo con que sea idiomático en C # y Java. ¿Con qué frecuencia te encuentras con esas frases, a menos que trabajes en el dominio de ingeniería / matemáticas, que, supongo, no constituye más del 1% del código Java / C # en la naturaleza?
Mladen Jablanović
6

A veces uso el incremento de postfix en las declaraciones de devolución. Por ejemplo, considera esto:

//Java
public class ArrayIterator implements Iterator<T> {
   private final T[] array;
   private int index = 0; 
   public ArrayIterator(T ... array) {
      this.array = array;
   }

   public T next {
      if (! hasNext()) throw new NoSuchElementException();
      return array[index++];   
   }
   ...
}

No puedes hacer esto con index += 1. Entonces, al menos la versión de postfix puede ahorrarte de vez en cuando algunas líneas.

Dicho esto, estos operadores no son necesarios y pueden confundir a las personas. Sé que Scala decidió en contra de estos operadores (solo tiene += y -=, como sugirió), y debido al enfoque más "de alto nivel", realmente no los extraño allí. Espero que Java se desarrolle (más lentamente) en la misma dirección.

Sin embargo, Java tiene muchos otros operadores que no se ven muy a menudo "en la naturaleza" (¿alguna vez usó ^=o >>>=?), Y a nadie le importa.

Landei
fuente
1
^=Es razonablemente común. Puede usarlo para voltear banderas de campo de bits, intercambiar dos enteros sin una variable temporal, y en algunos algoritmos de cifrado / hash. Sin >>>=embargo, admito que nunca he usado . (Por otra parte, nunca he usado >>>o <<<en ningún programa de producción ...)
Brian S
4

He construido muchos idiomas a lo largo de los años, en su mayoría con fines especiales. Lo que sucede es que tienes una gran audiencia, y todos tienen expectativas. Al considerar cada función, debe comparar el beneficio de la función con el costo de posiblemente violar las expectativas.

Uno en el que personalmente tuve que retroceder fue la división de enteros, que normalmente se trunca hacia abajo, ¿verdad? Me gusta 3 / 2 = 1.5 = 1, ¿verdad? Bueno, en los primeros días de Fortran, alguien decidió -3 / 2 = -1.5 = -1(no -2). En otras palabras, si el dividendo es negativo y el divisor es positivo, se debe truncar arriba . Tenga en cuenta lo que esto implica: en ese caso, el resto es negativo . Eso viola la suposición habitual de que resto = módulo. Si está haciendo gráficos, con coordenadas enteras, esto puede ocasionar todo tipo de problemas. Entonces, en el lenguaje simplemente hice que la división de enteros se truncara todo el tiempo, y lo dijera en el documento.

Y así, ¿adivina qué pasó? Todas las perras se soltaron. ¡El idioma tenía un error en la división de enteros!

No sirvió de nada decir que el viejo camino estaba roto. Era más fácil cambiarlo que discutirlo.

Entonces, para responder a su pregunta, en lenguajes como C # y Java, es simplemente más fácil incluir los operadores ++ (que personalmente me gustan) que explicar por qué no.

Mike Dunlavey
fuente
44
Como otro ejemplo, el lenguaje de programación D (que es, en muchos sentidos, el siguiente paso evolutivo de C ++) tiene una declaración de cambio, y mantuvieron los breaks requeridos en cada cláusula a pesar de que el lenguaje no permite fallar. Mientras que D descartó la compatibilidad hacia atrás de C ++ con C, su política es que cualquier construcción de C que D conserve debería sorprender al programador de C lo menos posible.
Doval
@Doval: Incluso construí un lenguaje que llamé D, como C pero con características de OO y paralelismo. No fue a ninguna parte (misericordiosamente) y no tiene relación con la D de la que estás hablando. Conduzco a la gente loca en C ?? escribiendo siempre break; case ...;-)
Mike Dunlavey
1
@MikeDunlavey: Prefiero decir que /rinde doubleo float(usando doubleen casos en los que cualquiera de los dos funcionaría), y tener operadores separados para la división en pisos, truncada y "hacer lo que sea", así como el módulo, el resto y "divisible por "[este último arroja un booleano]. Yo diría que cualquier resultado int x=y/z;tiene una probabilidad significativa de diferir de lo que se pretendía, por lo que la expresión no debería dar ningún resultado.
supercat
No es raro en los idiomas tener operadores separados para int-divide-by-int produciendo int o coma flotante. Además, el signo del resto (afecta el cociente) varía enormemente. Consulte en.wikipedia.org/wiki/Modulo_operation para obtener una lista de idiomas. Parte de la decisión de FORTRAN se basó sin duda en el hardware que utilizaron (¿IBM pre-360?) Y cómo se comportó con el dividendo y / o el divisor firmado.
Phil Perry
Sé que Pascal y Python separan a los operadores "cede el int" de los "cede el punto flotante", aunque no he visto ninguno que separe los modos de redondeo. Encuentro realmente asqueroso ese código que quiere, por ejemplo, calcular el promedio de tres números y podría escribirse en Python como (a+b+c)//3debe escribirse en la mayoría de los idiomas como un lío desagradable para lidiar con la semántica de división truncada (especialmente porque, en muchos procesadores , un piso dividido por tres podría hacerse más rápido que uno truncado!)
supercat
2

Sí, no es nada importante en sí mismo, su único beneficio es el azúcar sintáctico que hace que sea más fácil de escribir.

La razón por la que está en C en primer lugar es únicamente porque el PDP-11 que era la base de C, en los viejos tiempos, tenía una instrucción especial para aumentar en 1. En esos días, usar esta instrucción era importante para obtenga más velocidad del sistema, de ahí la función de idioma.

Sin embargo, creo que a la gente le gusta tanto el comando que se quejarían si no estuviera allí.

gbjbaanb
fuente
En aras de la precisión, no es tanto que el PDP-11 tuviera una instrucción para el incremento previo o posterior, sino que tuviera modos de direccionamiento (y decremento), lo que significa que cualquier instrucción que accediera a la memoria a través de un registro también podría se establecerá en un incremento o decremento previo o posterior que se registren al mismo tiempo. La tecnología de compilación en ese momento realmente no era capaz de hacerlo automáticamente a través de la optimización, por lo que el soporte explícito era la única forma de hacerlo funcionar.
Jules
2

Esta es una sintaxis idiomática en C para muchas situaciones (como el forbucle que citó), por lo que, como han dicho otros, puede ayudar a aquellos que hacen la transición a Java como un nuevo lenguaje y hace que sea más fácil portar código de estilo C preexistente. Por esas razones, incluir que era una decisión natural tomar en los primeros días de Java para acelerar la adopción del nuevo lenguaje. Ahora tiene menos sentido, porque el código más compacto se produce a expensas de exigir a los programadores que aprendan la sintaxis que no es estrictamente necesaria.

Quienes les gusta lo encuentran conveniente y natural. Hacerlo sin él parece incómodo. Usarlo no gana eficiencia, es cuestión de legibilidad. Si cree que sus programas son más fáciles de leer y mantener con estos operadores, úselos.

El sentido original en C era más que simplemente "sumar 1" o "restar 1". Es mucho más que azúcar sintáctica o pequeñas ganancias de rendimiento. El operador en C significa "incremento (o decremento) al siguiente elemento". Si tiene un puntero a algo más grande que un byte, y pasa a la siguiente dirección como en *pointer++, el incremento real podría ser de 4 u 8, o lo que sea necesario. El compilador hace el trabajo por usted, realiza un seguimiento del tamaño de los elementos de datos y omite el número correcto de bytes:

int k;
int *kpointer = &k;
*kpointer++ = 7;

En mi máquina, esto agrega 4 a kpointer, no agrega 1. Esta es una ayuda real en C, previniendo errores y guardando invocaciones espurias de sizeof (). No hay aritmética de puntero equivalente en Java, por lo que esto no es una consideración. Allí, se trata más del poder de la expresión que de cualquier otra cosa.

K. Eno
fuente