c++ - programacion - ¿Por qué es++ xa lvalue y x++ un rvalue?
preincremento c (6)
Esta pregunta ya tiene una respuesta aquí:
- lvalue y rvalue para pre / postfix incremento 1 respuesta
Así que he estado leyendo sobre lvalue y rvalues y estoy un poco confundido acerca de la diferencia entre
++x
y
x++
cuando se trata de esta categorización.
¿Por qué es
++x
a lvalue y
x++
a rvalue?
¿Por qué es ++ xa lvalue
Supongo que es un valor porque puede ser.
++x
puede pensarse como
((x = x + 1), x) // Increment x. Then evaluate to the value of x
o
((x += 1), x) // Increment x. Then evaluate to the value of x
Tiene sentido evaluar
++x
a un lvalue.
x ++ a rvalue?
Porque no puede ser un valor.
x++
se puede considerar como
((unnamed_variable = x), (x = x + 1), unnamed_variable)
o
((unnamed_variable = x), (x += 1), unnamed_variable)
x++
evalúa el valor de antes de que
x
se incremente.
Como no hay ninguna variable que pueda almacenar ese valor, no puede ser un valor l.
C ++ (a diferencia de C) es un lenguaje dedicado que preserva los valores: se esfuerza por preservar minuciosamente la "falta de valor" de una expresión siempre que sea posible.
-
Es muy fácil preservar la "lvaluidad" del pre-incremento : simplemente incremente el operando y devuélvalo como un valor. Hecho. El valor devuelto contendrá exactamente el resultado que debe contener: el nuevo valor (incrementado) del operando.
-
Y al mismo tiempo, es prácticamente imposible preservar la "falta de validez" del incremento posterior : por definición, el resultado del incremento posterior es el valor antiguo (original) del operando. Si intenta devolver un valor de l desde un incremento posterior, tendrá que asegurarse de manera simultánea de dos cosas: 1) el valor de l incrementa, 2) el código de llamada ve el valor antiguo cuando mira ese mismo valor de lorilla (!). Esta combinación de requisitos es tan contradictoria que es básicamente imposible de implementar en el modelo de objetos C ++.
Para implementar el comportamiento correcto posterior al incremento, uno debe asegurarse de que el código de llamada no mire directamente al operando, sino que busque un "proxy" conceptual o físico que haga que el código de llamada "vea" el valor antiguo del operando. Ese proxy podría ser un objeto temporal que contiene el valor antiguo. O ese proxy podría ser algo que genere el valor antiguo sobre la marcha al restar
1
del nuevo valor. En cualquier caso, ese proxy es lo que impide que el código de llamada acceda al valor original.
Esta es la razón por la que C ++ aprovecha la oportunidad fácilmente alcanzable para preservar la "falta de validez" del pre-incremento, pero admite la imposibilidad de lograr lo mismo con el post-incremento. En el caso de un incremento posterior, no vale la pena desviarse del comportamiento clásico estándar de C, que tiende a descartar la "falta de validez" de forma rápida y feliz.
En primer lugar, un "lvalue" es una expresión que es legal en el lado izquierdo (la "l" en "lvalue") de una asignación. Eso significa que representa una dirección cuyo contenido puede ser cambiado por la asignación. (El estándar C llama a tal cosa un "objeto".) Es por eso que, por ejemplo, las expresiones de operadores infijos y las llamadas a funciones no son valores.
"rvalue" es un término tonto; cualquier expresión bien formada es legal en el lado derecho de una asignación (dejando de lado los problemas de conversión de tipos).
La norma dice que, por ejemplo:
i = ++i + 1;
a[i++] = i;
..son ambas "expresiones de declaración no definidas", lo que significa que su implementación (y por lo tanto el comportamiento) no está estandarizada.
El significado de sentido común de "++ i" es "incrementar el valor del objeto i, y evaluar el resultado". De manera similar, "i ++" significa "evaluar el valor actual del objeto i, e incrementar el valor del objeto luego". Tampoco dice lo que sucede cuando se usa "++ i" o "i ++" como valor l.
El estándar dice, sobre las expresiones: "Los cálculos de valor de los operandos de un operador se secuencian antes del cálculo de valor del resultado del operador". Esto es realmente ambiguo para "++ i", ya que el cálculo del valor del operando se ve afectado por el cálculo del valor del operador.
Lo que tiene más sentido para mí es esto:
i = 5;
++i = i + 1; /*i is set to 6*/
i++ = 1; /*i is set to 1*/
++i = ++i + 1; /*i is set to 8, but problematic*/
i++ = i++ + 1; /*i is set to 8, but problematic*/
++i = i++ + 1; /*i is set to 8, but problematic*/
Mi consejo: ¡no uses expresiones que son tan difíciles de entender!
Tal vez escribir el funcionamiento en pseudocódigo de los operadores para
int
hace más claro:
prefijo:
int& Prefix(int& val)
{
int& value = val;
value += 1;
return value;
}
sufijo:
int Postfix(int& val)
{
int oldValue = val;
val += 1;
return oldValue;
}
La diferencia está en lo que devuelven los dos operadores, uno por valor y otro por referencia.
++x
devuelve una referencia al objeto que incrementó, mientras que
x++
devuelve una copia temporal del antiguo valor de
x
.
Al menos esta sería la forma "normal" de implementar estos operadores por convención. Y todos los tipos incorporados funcionan de esta manera. Y si ha leído sobre valores / valores, entonces verá que dado que el operador de prefijo devuelve el objeto con nombre en sí, sería un valor, donde el operador de postfix devuelve una copia de un temporal local, que luego calificaría como un valor nominal. .
Nota: Además, ahora tenemos prvalores, xvalores y demás, por lo que técnicamente es un poco más complicado en estos días. Busque aquí para más información.
Para los tipos incorporados
, como
int
,
x++
produce el valor antiguo de
x
.
No hay almacenamiento asociado con esto, por lo que sería imposible que la expresión fuera un valor l.
En C,
++x
produjo el nuevo valor de
x
(y no era un valor de l).
Dado que el objeto
x
realmente contiene ese mismo valor, es posible que
++x
designe el objeto
x
(es decir, que sea un valor l).
Esta es una funcionalidad adicional en comparación con la producción de un valor de r, y el diseñador de C ++ decidió que esto sería una mejora del lenguaje.
Para los tipos de clase
, es posible sobrecargar a cualquier operador de tal manera que el uso del operador pueda producir un valor lvalue, xvalue o prvalue.
Sin embargo, se considera que es un buen estilo hacer que los operadores sobrecargados tengan una semántica similar a los operadores incorporados, por lo que es normal que las personas sobrecarguen
++x
para producir un lvalor y para sobrecargar
x++
para generar un rvalor.