En programación de computadoras , una declaración de asignación establece y/o restablece el valor almacenado en la ubicación de almacenamiento indicada por un nombre de variable ; en otras palabras, copia un valor en la variable. En la mayoría de los lenguajes de programación imperativos , la declaración (o expresión) de asignació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 idiomas, 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 usar en una expresión).x = expr
x := expr
Las asignaciones generalmente permiten que una variable mantenga 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 imponer la transparencia referencial , es decir, funciones que no dependen del estado de algunas variables, pero que producen los mismos resultados para un conjunto determinado de entradas paramétricas en cualquier momento. Los programas modernos en otros lenguajes también suelen utilizar estrategias similares, aunque menos estrictas, y sólo en determinadas partes, con el fin de reducir la complejidad, normalmente en conjunto 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 de 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 utilizando declaraciones de asignación sucesivas. [2] [3] Los primitivos de los lenguajes de programación imperativos se basan en la asignación para realizar la iteración . [4] En el nivel más bajo, la asignación se implementa mediante operaciones de máquina como MOVE
o STORE
. [2] [4]
Las variables son contenedores de valores. Es posible poner 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 tarea:
expression
evalúa 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 :
entero x = 10 ; flotar 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 ocurren en la misma declaración. En la segunda línea, y
se declara sin cesión. En la tercera línea, x
se reasigna el valor de 23. Finalmente, y
se asigna el valor de 32,4.
Para una operación de asignación, es necesario que el valor de expression
esté bien definido (es un rvalue válido ) y que variable
represente una entidad modificable (es un lvalue modificable (no constante ) válido ). 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 asigna, y el alcance en el que se declara varía según el idioma.
Cualquier asignación que cambie un valor existente (p. ej. x := x + 1
) no está permitida en lenguajes puramente funcionales . [4] En programación funcional , se desaconseja la asignación en favor de la asignación única, más comúnmente conocida como inicialización . La asignación única es un ejemplo de vinculación de nombres y difiere de la asignación descrita en este artículo en que solo se puede realizar una vez, generalmente cuando se crea la variable; no se permite ninguna reasignación posterior.
Una evaluación de una expresión no tiene efectos secundarios si no cambia un estado observable de la máquina, [5] aparte 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 esa razón se conoce como asignación destructiva en LISP y programación funcional , similar a la actualización destructiva .
La asignación única 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 con nombre posiblemente de naturaleza compuesta, con sus elementos definidos progresivamente en demanda , para las lenguas perezosas . Los lenguajes puramente funcionales pueden brindar la oportunidad de realizar cálculos en paralelo , evitando el cuello de botella de von Neumann de la ejecución secuencial paso a paso, ya que los valores son independientes entre sí. [7]
Los lenguajes funcionales impuros proporcionan tanto una asignación única como una asignación verdadera (aunque la asignación verdadera generalmente se usa con menos frecuencia que en los lenguajes de programación imperativos). Por ejemplo, en Scheme, se pueden utilizar tanto la asignación única (con let
) como la asignación verdadera (con set!
) en todas las variables, y se proporcionan primitivas especializadas para actualizaciones destructivas dentro de listas, vectores, cadenas, etc. En OCaml, solo se permite la asignación única para variables, mediante la sintaxis; sin embargo, la actualización destructiva se puede utilizar en elementos de matrices y cadenas con operador separado, así como en campos de registros y objetos que el programador ha declarado explícitamente mutables (es decir, que pueden cambiarse después de su declaración inicial).let name = value
<-
Los lenguajes de programación funcionales que usan asignación única incluyen Clojure (para estructuras de datos, no vars), Erlang (acepta asignaciones múltiples 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 vals), SISAL , Standard ML . El código Prolog sin seguimiento puede considerarse de asignación única explícita , explícito en el sentido de que sus variables (nombradas) pueden estar en un estado explícitamente no asignado o establecerse exactamente una vez. En Haskell, por el contrario, no puede haber variables no asignadas, y se puede pensar que cada variable está implícitamente establecida, 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 declaración de asignación devuelve el valor asignado, lo que permite modismos como x = y = a
, en los que la declaración de asignación y = a
devuelve el valor de a
, que luego se asigna a x
. En una declaración como , el valor de retorno de una función se usa 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) generalmente se evalúan como el tipo de unidad , que se representa como ()
. Este tipo tiene sólo un valor posible, por lo que no contiene información. Por lo general, es el tipo de 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 para respaldarlos. Se trata principalmente de azúcar sintáctico 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, sobre todo C y la mayoría de sus descendientes, proporcionan operadores especiales llamados asignación aumentada , como *=
, por lo que a = 2*a
en su lugar se puede escribir como a *= 2
. [3] Más allá del azúcar sintáctico, esto ayuda a la tarea del compilador al dejar claro que a
es posible la modificación in situ de la variable.
Una declaración como w = x = y = z
se llama 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 la asignación encadenada. Las tareas encadenadas equivalen a una secuencia de tareas, pero la estrategia de evaluación difiere según el idioma. Para tareas encadenadas simples, como inicializar múltiples variables, la estrategia de evaluación no importa, pero si los objetivos (valores l) en la tarea están conectados de alguna manera, la estrategia de evaluación afecta el resultado.
En algunos lenguajes de programación ( C por ejemplo), se admiten asignaciones encadenadas porque las asignaciones son expresiones y tienen valores. En este caso, la asignación en cadena se puede implementar teniendo una asignación asociativa por la derecha , y las asignaciones se realizan de derecha a izquierda. Por ejemplo, i = arr[i] = f()
equivale a arr[i] = f(); i = arr[i]
. En C++ también están disponibles para valores de tipos de clase declarando el tipo de retorno apropiado para el operador de asignación.
En Python , las declaraciones de asignación no son expresiones y, por lo tanto, no tienen valor. En cambio, las asignaciones encadenadas son una serie de declaraciones con múltiples objetivos para una sola expresión. Las asignaciones se ejecutan de izquierda a derecha para que i = arr[i] = f()
evalúe la expresión f()
, luego asigne el resultado al objetivo más a la izquierda, i
y luego asigne el mismo resultado al siguiente objetivo, arr[i]
usando el nuevo valor de i
. [9] Esto es esencialmente equivalente a tmp = f(); i = tmp; arr[i] = tmp
aunque no se produzca 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 sintaxis como:
a, b := 0, 1
que asigna simultáneamente 0 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 le llama asignación múltiple , aunque esto resulta confuso cuando se usa con "asignación única", 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 de desestructuración : [18]
lista de variables : = {0, 1}a, b := lista
La lista se descomprimirá para asignar 0 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 tendría que escribirse para usar una variable temporal
var t := aa := bsegundo := t
ya que a := b; b := a
deja ambos a
y 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 entre paréntesis:
// Sintaxis válida de C# o Rust ( a , b ) = ( b , a );
// retorno de tupla de C# ( cadena , int ) f () => ( "foo" , 1 ); var ( a , b ) = f ();
// retorno de tupla de Rust let f = || ( "foo" , 1 ); sea ( 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# además permite la asignación de deconstrucción generalizada con implementación definida por la expresión del lado derecho, ya que el compilador busca una instancia apropiada o un método de extensión Deconstruct
en la expresión, que debe tener parámetros de salida para las variables a las que se asigna. [19] Por ejemplo, uno de esos métodos que le daría a la clase el mismo comportamiento que el valor de retorno f()
anterior sería
void Deconstruct ( fuera cadena a , fuera int b ) { a = "foo" ; segundo = 1 ; }
En C y C++, el operador de coma es similar a la asignación paralela al permitir que se realicen múltiples asignaciones dentro de una sola declaración, escribiendo a = 1, b = 2
en lugar de a, b = 1, 2
. Esto se usa principalmente en bucles for y se reemplaza por 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 la nueva b.
El uso del signo igual =
como operador de asignación ha sido criticado con frecuencia, debido al conflicto con iguales como comparación de igualdad. Esto resulta tanto en confusión para los principiantes en la escritura de código como en confusión incluso para los programadores experimentados en la lectura de código. El uso de iguales 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 mala idea fue la elección del signo igual para indicar una 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 permitir que "=" denote una comparación de igualdad, un predicado que es verdadero o falso. Pero Fortran entendió que significaba asignación, el cumplimiento de la igualdad. En este caso, los operandos están en condiciones desiguales: el operando izquierdo (una variable) debe igualarse al operando derecho (una expresión). x = y no significa lo mismo que y = x. [21]
— Niklaus Wirth , 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 idiomas. 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 único signo igual ( "="
) tanto para el operador de asignación como para el operador relacional de igualdad, y el contexto determina a qué se refiere. Otros idiomas utilizan símbolos diferentes para los dos operadores. [22] Por ejemplo:
":="
), mientras que el operador de igualdad es un solo igual ( "="
)."="
) mientras que el operador de igualdad es un par de signos igual ( "=="
).<-
, como en x <- value
, pero en ciertos contextos se puede usar un solo signo igual.La similitud entre los dos símbolos puede provocar errores si el programador olvida qué forma (" =
", " ==
", " :=
") es apropiada, o escribe mal " =
" cuando ==
se pretendía " ". Este es un problema de programación común con lenguajes como C (incluido un famoso intento de abrir 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 estar válidamente anidado 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 la then
cláusula se ejecutará, lo que provocará que el programa se comporte inesperadamente. Algunos procesadores de lenguaje (como gcc ) pueden detectar este tipo de situaciones y advertir al programador del posible error. [24] [25]
Las dos representaciones más comunes para la tarea de copia son el signo igual ( =
) y dos puntos iguales ( :=
). 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 idioma y/o el uso.
Otras posibilidades incluyen una flecha hacia la izquierda o una palabra clave, aunque existen otras variantes, más raras:
Las asignaciones de pseudocódigos matemáticos generalmente se representan con una flecha hacia la izquierda.
Algunas plataformas ponen 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 postfijo) para todas las declaraciones, incluida la asignación.
=
Fortran es anterior, aunque fue popularizado por Fortran.