En programación informática , una sentencia de asignación establece o restablece el valor almacenado en la(s) ubicación(es) de almacenamiento indicadas por un nombre de variable ; en otras palabras, copia un valor en la variable. En la mayoría de los lenguajes de programación imperativa , la sentencia de asignación (o expresión) es una construcción fundamental.
Hoy en día, la notación más utilizada para esta operación es (originalmente Superplan 1949–51, popularizada por Fortran 1957 y C ). La segunda notación más utilizada es [1] (originalmente ALGOL 1958, popularizada por Pascal ). [2] También se utilizan muchas otras notaciones. En algunos lenguajes, el símbolo utilizado se considera un operador (lo que significa que la declaración de asignación en su conjunto devuelve un valor). Otros lenguajes definen la asignación como una declaración (lo que significa que no se puede utilizar en una expresión).x = expr
x := expr
Las asignaciones suelen permitir que una variable contenga diferentes valores en diferentes momentos durante su vida útil y alcance . Sin embargo, algunos lenguajes (principalmente lenguajes estrictamente funcionales ) no permiten ese tipo de reasignación "destructiva", ya que podría implicar cambios de estado no local. El propósito es hacer cumplir la transparencia referencial , es decir, funciones que no dependen del estado de alguna variable(s), pero que producen los mismos resultados para un conjunto dado de entradas paramétricas en cualquier momento. Los programas modernos en otros lenguajes también suelen utilizar estrategias similares, aunque menos estrictas, y solo en ciertas partes, para reducir la complejidad, normalmente junto con metodologías complementarias como la estructuración de datos , la programación estructurada y la orientación a objetos .
Una operación de asignación es un proceso en programación imperativa en el que se asocian diferentes valores con un nombre de variable particular a medida que pasa el tiempo. [1] El programa, en dicho modelo, opera cambiando su estado mediante declaraciones de asignación sucesivas. [2] [3] Las primitivas de los lenguajes de programación imperativa se basan en la asignación para realizar la iteración . [4] En el nivel más bajo, la asignación se implementa utilizando operaciones de máquina como MOVE
o STORE
. [2] [4]
Las variables son contenedores de valores. Es posible introducir un valor en una variable y luego reemplazarlo por uno nuevo. Una operación de asignación modifica el estado actual del programa en ejecución. [3] En consecuencia, la asignación depende del concepto de variables . En una asignación:
expression
en el estado actual del programa.variable
le asigna el valor calculado, reemplazando el valor anterior de esa variable.Ejemplo: Suponiendo que a
es una variable numérica, la asignación a := 2*a
significa que el contenido de la variable a
se duplica después de la ejecución de la declaración.
Un segmento de ejemplo de código C :
int x = 10 ; flotante y ; x = 23 ; y = 32.4f ;
En este ejemplo, la variable x
se declara primero como int y luego se le asigna el valor 10. Observe que la declaración y la asignación se producen en la misma instrucción. En la segunda línea, y
se declara sin asignación. En la tercera línea, x
se le reasigna el valor 23. Finalmente, y
se le asigna el valor 32,4.
Para una operación de asignación, es necesario que el valor de expression
esté bien definido (sea un rvalue válido ) y que variable
represente una entidad modificable (sea un lvalue modificable válido (no constante ) . En algunos lenguajes, normalmente los dinámicos , no es necesario declarar una variable antes de asignarle un valor. En dichos lenguajes, una variable se declara automáticamente la primera vez que se le asigna, y el ámbito en el que se declara varía según el lenguaje.
Cualquier asignación que cambie un valor existente (por ejemplo, x := x + 1
) no está permitida en lenguajes puramente funcionales . [4] En la programación funcional , la asignación se desaconseja en favor de la asignación única, más comúnmente conocida como inicialización . La asignación única es un ejemplo de enlace de nombre y se diferencia de la asignación como se describe en este artículo en que solo se puede hacer una vez, generalmente cuando se crea la variable; no se permite ninguna reasignación posterior.
Una evaluación de una expresión no tiene un efecto secundario si no cambia un estado observable de la máquina, [5] además de producir el resultado, y siempre produce el mismo valor para la misma entrada. [4] La asignación imperativa puede introducir efectos secundarios al destruir y hacer que el valor anterior no esté disponible al sustituirlo por uno nuevo, [6] y por ese motivo se la conoce como asignación destructiva en LISP y en programación funcional , de forma similar a la actualización destructiva .
La asignación simple es la única forma de asignación disponible en lenguajes puramente funcionales, como Haskell , que no tienen variables en el sentido de los lenguajes de programación imperativos [4] sino valores constantes nombrados posiblemente de naturaleza compuesta, con sus elementos definidos progresivamente a demanda , para los lenguajes perezosos . Los lenguajes puramente funcionales pueden proporcionar una oportunidad para que el cálculo se realice en paralelo , evitando el cuello de botella de von Neumann de la ejecución secuencial de un paso a la vez, ya que los valores son independientes entre sí. [7]
Los lenguajes funcionales impuros proporcionan tanto la asignación simple como la asignación verdadera (aunque la asignación verdadera se usa típicamente con menos frecuencia que en los lenguajes de programación imperativos). Por ejemplo, en Scheme, tanto la asignación simple (con let
) como la asignación verdadera (con set!
) se pueden usar en todas las variables, y se proporcionan primitivas especializadas para la actualización destructiva dentro de listas, vectores, cadenas, etc. En OCaml, solo se permite la asignación simple para las variables, a través de la sintaxis; sin embargo, la actualización destructiva se puede usar en elementos de matrices y cadenas con un operador separado, así como en campos de registros y objetos que han sido declarados explícitamente mutables (lo que significa que pueden cambiarse después de su declaración inicial) por el programador.let name = value
<-
Los lenguajes de programación funcional que utilizan asignación única incluyen Clojure (para estructuras de datos, no variables), Erlang (acepta asignación múltiple si los valores son iguales, a diferencia de Haskell), F# , Haskell , JavaScript (para constantes), Lava, OCaml , Oz (para variables de flujo de datos, no celdas), Racket (para algunas estructuras de datos como listas, no símbolos), SASL , Scala (para valores), SISAL y Standard ML . El código Prolog sin retroceso puede considerarse de asignación única explícita , explícita en el sentido de que sus variables (nombradas) pueden estar en estado explícitamente no asignado o configurarse exactamente una vez. En Haskell, por el contrario, no puede haber variables sin asignar y se puede pensar que cada variable se configura implícitamente, cuando se crea, a su valor (o más bien a un objeto computacional que producirá su valor a pedido ).
En algunos lenguajes de programación, una declaración de asignación devuelve un valor, mientras que en otros no.
En la mayoría de los lenguajes de programación orientados a expresiones (por ejemplo, C ), la sentencia de asignación devuelve el valor asignado, lo que permite expresiones como x = y = a
, en la que la sentencia de asignación y = a
devuelve el valor de a
, que luego se asigna a x
. En una sentencia como , el valor de retorno de una función se utiliza para controlar un bucle mientras se asigna ese mismo valor a una variable.while ((ch = getchar()) != EOF) {…}
En otros lenguajes de programación, Scheme por ejemplo, el valor de retorno de una asignación no está definido y dichos modismos no son válidos.
En Haskell , [8] no hay asignación de variables; pero las operaciones similares a la asignación (como la asignación a un campo de una matriz o a un campo de una estructura de datos mutable) normalmente evalúan el tipo de unidad , que se representa como ()
. Este tipo tiene solo un valor posible, por lo que no contiene información. Normalmente es el tipo de una expresión que se evalúa únicamente por sus efectos secundarios.
Ciertos patrones de uso son muy comunes y, por lo tanto, suelen tener una sintaxis especial que los respalda. Se trata principalmente de azúcar sintáctica para reducir la redundancia en el código fuente, pero también ayuda a los lectores del código a comprender la intención del programador y proporciona al compilador una pista para una posible optimización.
El caso en el que el valor asignado depende de uno anterior es tan común que muchos lenguajes imperativos, especialmente C y la mayoría de sus descendientes, proporcionan operadores especiales llamados asignación aumentada , como *=
, por lo que a = 2*a
puede escribirse como a *= 2
. [3] Más allá del azúcar sintáctico, esto ayuda a la tarea del compilador al dejar en claro que a
es posible la modificación en el lugar de la variable.
Una declaración como w = x = y = z
se denomina asignación encadenada en la que el valor de z
se asigna a múltiples variables w, x,
y y
. Las asignaciones encadenadas se utilizan a menudo para inicializar múltiples variables, como en
a = b = c = d = f = 0
No todos los lenguajes de programación admiten asignaciones encadenadas. Las asignaciones encadenadas son equivalentes a una secuencia de asignaciones, pero la estrategia de evaluación difiere entre lenguajes. Para asignaciones encadenadas simples, como la inicialización de múltiples variables, la estrategia de evaluación no importa, pero si los objetivos (valores l) en la asignación están conectados de alguna manera, la estrategia de evaluación afecta el resultado.
En algunos lenguajes de programación ( por ejemplo, C ), se admiten las asignaciones encadenadas porque las asignaciones son expresiones y tienen valores. En este caso, la asignación en cadena se puede implementar con una asignación asociativa por la derecha y las asignaciones se realizan de derecha a izquierda. Por ejemplo, i = arr[i] = f()
es equivalente a arr[i] = f(); i = arr[i]
. En C++, también están disponibles para valores de tipos de clase al declarar el tipo de retorno apropiado para el operador de asignación.
En Python , las instrucciones de asignación no son expresiones y, por lo tanto, no tienen un valor. En cambio, las asignaciones encadenadas son una serie de instrucciones con múltiples destinos para una sola expresión. Las asignaciones se ejecutan de izquierda a derecha, de modo que i = arr[i] = f()
evalúa la expresión f()
, luego asigna el resultado al destino más a la izquierda, i
y luego asigna el mismo resultado al siguiente destino, arr[i]
, utilizando el nuevo valor de i
. [9] Esto es esencialmente equivalente a tmp = f(); i = tmp; arr[i] = tmp
aunque no se produce ninguna variable real para el valor temporal.
Algunos lenguajes de programación, como APL , Common Lisp , [10] Go , [11] JavaScript (desde 1.7), PHP , Maple , Lua , occam 2 , [12] Perl , [13] Python , [14] REBOL , Ruby , [15] y PowerShell permiten asignar varias variables en paralelo, con una sintaxis como:
a, b := 0, 1
que asigna simultáneamente 0 a a
y 1 a b
. Esto se conoce más comúnmente como asignación paralela ; se introdujo en CPL en 1963, bajo el nombre de asignación simultánea , [16] y a veces se llama asignación múltiple , aunque esto es confuso cuando se usa con "asignación simple", ya que no son opuestos. Si el lado derecho de la asignación es una sola variable (por ejemplo, una matriz o estructura), la característica se llama desempaquetado [17] o asignación desestructurante : [18]
lista var := {0, 1}a, b := lista
La lista se descomprimirá de modo que se asigne 0 a a
y 1 a b
. Además,
a, b := b, a
intercambia los valores de a
y b
. En lenguajes sin asignación paralela, esto debería escribirse para utilizar una variable temporal
var t := aa := bb := t
ya que a := b; b := a
deja tanto a
como b
con el valor original de b
.
Algunos lenguajes, como Go , F# y Python , combinan asignación paralela, tuplas y desempaquetado automático de tuplas para permitir múltiples valores de retorno de una sola función, como en este ejemplo de Python,
def f (): devuelve 1 , 2 a , b = f ()
mientras que otros lenguajes, como C# y Rust , que se muestran aquí, requieren la construcción y deconstrucción explícita de tuplas con paréntesis:
// Sintaxis válida de C# o Rust ( a , b ) = ( b , a );
// Tupla de C# return ( cadena , int ) f () => ( "foo" , 1 ); var ( a , b ) = f ();
// Tupla de Rust devuelve let f = || ( "foo" , 1 ); let ( a , b ) = f ();
Esto proporciona una alternativa al uso de parámetros de salida para devolver múltiples valores de una función. Esto data de CLU (1974), y CLU ayudó a popularizar la asignación paralela en general.
C# también permite la asignación de deconstrucción generalizada con la implementación definida por la expresión en el lado derecho, ya que el compilador busca una instancia o método de extensión Deconstruct
apropiado en la expresión, que debe tener parámetros de salida para las variables a las que se asigna. [19] Por ejemplo, un método de este tipo que daría a la clase en la que aparece el mismo comportamiento que el valor de retorno de f()
arriba sería
void Deconstruct ( out cadena a , out int b ) { a = "foo" ; b = 1 ; }
En C y C++, el operador de coma es similar a la asignación paralela al permitir que se produzcan múltiples asignaciones dentro de una sola declaración, escribiendo a = 1, b = 2
en lugar de a, b = 1, 2
. Esto se utiliza principalmente en bucles for y se reemplaza por la asignación paralela en otros lenguajes como Go. [20]
Sin embargo, el código C++ anterior no garantiza una simultaneidad perfecta, ya que el lado derecho del siguiente código a = b, b = a+1
se evalúa después del lado izquierdo. En lenguajes como Python, a, b = b, a+1
asignará las dos variables simultáneamente, utilizando el valor inicial de a para calcular el nuevo b.
El uso del signo igual =
como operador de asignación ha sido criticado con frecuencia debido al conflicto que genera con el signo igual como operador de comparación para la igualdad. Esto genera confusión tanto para los novatos a la hora de escribir código como para los programadores experimentados a la hora de leerlo. El uso del signo igual para la asignación se remonta al lenguaje Superplan de Heinz Rutishauser , diseñado entre 1949 y 1951, y fue particularmente popularizado por Fortran:
Un ejemplo notorio de una mala idea fue la elección del signo igual para denotar la asignación. Se remonta a Fortran en 1957 [a] y ha sido copiado ciegamente por ejércitos de diseñadores de lenguajes. ¿Por qué es una mala idea? Porque derriba una tradición centenaria de dejar que “=” denote una comparación de igualdad, un predicado que es verdadero o falso. Pero Fortran lo hizo para que significara asignación, la imposición de la igualdad. En este caso, los operandos están en pie de igualdad: el operando izquierdo (una variable) debe hacerse igual al operando derecho (una expresión). x = y no significa lo mismo que y = x. [21]
— Niklaus Wirth , Las buenas ideas a través del espejo
Los programadores principiantes a veces confunden la asignación con el operador relacional de igualdad, ya que "=" significa igualdad en matemáticas y se utiliza para la asignación en muchos lenguajes. Pero la asignación altera el valor de una variable, mientras que la prueba de igualdad prueba si dos expresiones tienen el mismo valor.
En algunos lenguajes, como BASIC , "="
se utiliza un solo signo igual ( ) tanto para el operador de asignación como para el operador relacional de igualdad, y el contexto determina cuál de ellos se utiliza. Otros lenguajes utilizan símbolos diferentes para los dos operadores. [22] Por ejemplo:
":="
), mientras que el operador de igualdad es un solo signo igual ( "="
)."="
) mientras que el operador de igualdad es un par de signos iguales ( "=="
).<-
, como en x <- value
, pero se puede usar un solo signo igual en ciertos contextos.La similitud entre los dos símbolos puede llevar a errores si el programador olvida qué forma (" =
", " ==
", " :=
") es la apropiada, o escribe mal " =
" cuando se pretendía " ==
". Este es un problema de programación común con lenguajes como C (incluyendo un famoso intento de hacer una puerta trasera al kernel de Linux), [23] donde el operador de asignación también devuelve el valor asignado (de la misma manera que una función devuelve un valor), y puede anidarse válidamente dentro de expresiones. Si la intención era comparar dos valores en una if
declaración, por ejemplo, es muy probable que una asignación devuelva un valor interpretable como booleano verdadero, en cuyo caso then
se ejecutará la cláusula, lo que hará que el programa se comporte de manera inesperada. Algunos procesadores de lenguaje (como gcc ) pueden detectar tales situaciones y advertir al programador del posible error. [24] [25]
Las dos representaciones más comunes para la asignación de copia son el signo igual ( =
) y dos puntos-igual ( :=
). Ambas formas pueden denotar semánticamente una declaración de asignación o un operador de asignación (que también tiene un valor), según el lenguaje y/o el uso.
Otras posibilidades incluyen una flecha izquierda o una palabra clave, aunque existen otras variantes más raras:
Las asignaciones de pseudocódigo matemático generalmente se representan con una flecha hacia la izquierda.
Algunas plataformas colocan la expresión a la izquierda y la variable a la derecha:
Algunos lenguajes orientados a expresiones, como Lisp [34] [35] y Tcl, utilizan uniformemente la sintaxis de prefijo (o sufijo) para todas las declaraciones, incluida la asignación.
=
es anterior a Fortran, aunque fue popularizado por Fortran.