В компьютерном программировании оператор присваивания устанавливает и / или повторно -задает значение , хранящееся в ячейках памяти, обозначенных переменной именем ; другими словами, он копирует значение в переменную. В большинстве императивных языков программирования оператор присваивания (или выражение) является фундаментальной конструкцией.
Сегодня наиболее часто используемым обозначением для этой базовой операции стало x = expr
(первоначально Superplan 1949–51, популяризировано Fortran 1957 и C ), за которым следует x: = expr
(первоначально АЛГОЛ 1958, популяризированный Паскалем ), хотя есть много другие используемые обозначения. В некоторых языках используемый символ рассматривается как оператор (что означает, что оператор присваивания в целом возвращает значение), в то время как другие определяют присвоение как оператор (что означает, что его нельзя использовать в выражении).
Назначения обычно позволяют переменной сохранять разные значения в разное время в течение ее жизненного цикла и области действия. Однако некоторые языки (в основном строго функциональный ) не допускают такого «деструктивного» переназначения, так как это может означать изменения нелокального состояния. Цель состоит в том, чтобы обеспечить ссылочную прозрачность, то есть функции, которые не зависят от состояния некоторой переменной (переменных), но производят одинаковые результаты для заданного набора параметрических входных данных в любой момент времени. Современные программы на других языках также часто используют аналогичные стратегии, хотя и менее строгие, и только в определенных частях, чтобы уменьшить сложность, обычно в сочетании с дополнительными методологиями, такими как структурирование данных, структурное программирование и ориентация объекта.
Операция присваивания - это процесс в императивном программировании, в котором различные значения связаны с конкретным именем переменной по прошествии времени. Программа в такой модели работает, изменяя свое состояние, используя последовательные операторы присваивания. Примитивы императивных языков программирования полагаются на присваивание для выполнения итерации. На самом низком уровне назначение осуществляется с помощью машинных операций, таких как MOVE
или STORE
.
Переменные являются контейнерами для значений. Можно поместить значение в переменную, а затем заменить его новым. Операция присваивания изменяет текущее состояние исполняемой программы. Следовательно, присвоение зависит от концепции переменных. В присвоении:
оценивается в текущем состоянии программы.
назначается вычисленное значение, заменяющее предыдущее значение эту переменную.Пример: Предполагая, что a
является числовой переменной, присвоение a: = 2 * a
означает, что содержимое переменной a
удваивается после выполнения оператора.
Пример сегмента кода C :
int x = 10; float y; х = 23; y = 32,4f;
В этом примере переменная x
сначала объявляется как int, а затем ей присваивается значение 10. Обратите внимание, что объявление и присваивание выполняются в одном операторе. Во второй строке y
объявлен без присваивания. В третьей строке x
повторно присваивается значение 23. Наконец, y
присваивается значение 32,4.
Для операции присваивания необходимо, чтобы значение выражения
было четко определено (это действительное rvalue ) и чтобы переменная представляет изменяемую сущность (это допустимое изменяемое (не const ) lvalue ). В некоторых языках, обычно динамических, нет необходимости объявлять переменную до присвоения ей значения. В таких языках переменная автоматически объявляется при первом назначении, а объем ее объявления зависит от языка.
Любое присвоение, изменяющее существующее значение (например, x: = x + 1
), запрещено в чисто функциональных языках. В функциональном программировании назначение не рекомендуется в пользу однократного назначения, также называемого инициализацией. Одиночное присвоение является примером привязки имени и отличается от присвоения, описанного в этой статье, тем, что оно может быть выполнено только один раз, обычно при создании переменной; последующее переназначение не допускается.
Оценка выражения не имеет побочного эффекта , если она не изменяет наблюдаемое состояние машины и дает те же значения для того же ввода. Императивное присвоение может привести к побочным эффектам при разрушении и создании недоступности старого значения при замене его новым, поэтому в LISP и функциональное программирование оно называется деструктивным присвоением, аналогично деструктивному обновлению.
Одиночное присваивание - единственная форма присваивания, доступная в чисто функциональных языках, таких как Haskell, которые не имеют переменных в смысле императивных языков программирования, а имеют именованные константы ценности, возможно, комплексного характера с их элементами, постепенно определяемыми по запросу. Чисто функциональные языки могут обеспечить возможность параллельного выполнения вычислений , избегая узкого места фон Неймана последовательного выполнения одного шага во времени, поскольку значения не зависят друг от друга.
Нечеткие функциональные языки обеспечивают как одиночное присваивание, так и истинное присваивание (хотя истинное присваивание обычно используется с меньшей частотой, чем в императивных языках программирования). Например, в Scheme для всех переменных можно использовать как одиночное присвоение (с let
), так и истинное присвоение (с set!
), а для деструктивного обновления внутри списков предусмотрены специализированные примитивы., векторы, строки и т. д. В OCaml для переменных разрешено только одиночное присваивание с помощью синтаксиса let name = value
; однако деструктивное обновление может использоваться для элементов массивов и строк с отдельным оператором <-
, а также для полей записей и объектов, которые были явно объявлены изменяемыми (что означает возможность изменения после их первоначального объявления) программистом.
Функциональные языки программирования, использующие одиночное присваивание, включают Clojure (для структур данных, а не переменных), Erlang (допускает множественное присваивание, если значения равны, в отличие от в Haskell), F#, Haskell, Lava, OCaml, Oz (для переменных потока данных, а не ячеек), Racket (для некоторых структур данных, например списков, а не символы), SASL, Scala (для vals), SISAL, Standard ML. Код без с возвратом Prolog можно рассматривать как явное однократное присваивание, явное в том смысле, что его (именованные) переменные могут находиться в явно неназначенном состоянии или устанавливаться ровно один раз. В Haskell, напротив, не может быть неназначенных переменных, и каждая переменная может рассматриваться как неявно установленная на свое значение (или, скорее, на вычислительный объект, который будет генерировать свое значение по запросу) при ее создании.
В некоторых языках программирования оператор присваивания возвращает значение, а в других - нет.
В большинстве языков программирования, ориентированных на выражения (например, C ), оператор присваивания возвращает присвоенное значение, допуская такие идиомы, как x = y = a
, в котором оператор присваивания y = a
возвращает значение a
, которое затем присваивается x
. В таком операторе, как while ((ch = getchar ())! = EOF) {…}
, возвращаемое значение функции используется для управления циклом при присвоении того же значения переменной.
В других языках программирования, например Схема, возвращаемое значение присваивания не определено, и такие идиомы недопустимы.
В Haskell нет назначения переменных; но операции, аналогичные присваиванию (например, присвоение полю массива или полю изменяемой структуры данных) обычно оцениваются по типу блока , который представлен как ()
. Этот тип имеет только одно возможное значение, поэтому не содержит информации. Обычно это тип выражения, который оценивается исключительно на предмет его побочных эффектов.
Определенные шаблоны использования очень распространены и поэтому часто имеют специальный синтаксис для их поддержки. Это в первую очередь синтаксический сахар для уменьшения избыточности в исходном коде, но также помогает читателям кода понять намерения программиста и дает компилятору ключ к возможной оптимизации.
Случай, когда присвоенное значение зависит от предыдущего, настолько распространен, что многие императивные языки, в первую очередь C и большинство его потомков, предоставляют специальные операторы, называемые расширенным присваиванием, например * =
, поэтому a = 2 * a
вместо этого можно записать как a * = 2
. Помимо синтаксического сахара, это упрощает задачу компилятора, давая понять, что возможна модификация на месте переменной a
.
Оператор вида w = x = y = z
называется цепным присвоением, в котором значение z
присваивается нескольким переменным w, x,
и y
. Цепные присвоения часто используются для инициализации нескольких переменных, как в
a = b = c = d = f = 0
Не все языки программирования поддерживают сцепленное присвоение. Связанные задания эквивалентны последовательности заданий, но стратегия оценки различается в зависимости от языка. Для простых связанных назначений, таких как инициализация нескольких переменных, стратегия оценки не имеет значения, но если цели (l-значения) в назначении каким-либо образом связаны, стратегия оценки влияет на результат.
В некоторых языках программирования (например, C ) цепные присваивания поддерживаются, поскольку присваивания являются выражениями и имеют значения. В этом случае цепное присвоение может быть реализовано с помощью правоассоциативного присвоения, и присвоения происходят справа налево. Например, i = arr [i] = f ()
эквивалентно arr [i] = f (); i = arr [i]
. В C ++ они также доступны для значений типов классов путем объявления соответствующего возвращаемого типа для оператора присваивания.
В Python операторы присваивания не являются выражениями и, следовательно, не имеют значения. Вместо этого связанные присваивания представляют собой серию операторов с несколькими целевыми объектами для одного выражения. Присваивания выполняются слева направо, так что i = arr [i] = f ()
вычисляет выражение f ()
, затем присваивает результат самой левой цели, i
, а затем присваивает тот же результат следующей цели, arr [i]
, используя новое значение i
. По сути, это эквивалент tmp = f (); я = tmp; arr [i] = tmp
, хотя фактическая переменная для временного значения не создается.
Некоторые языки программирования, такие как APL, Common Lisp,Go,JavaScript (начиная с версии 1.7), PHP, Maple, Lua, occam 2, Perl, Python, REBOL, Ruby и PowerShell позволяют назначать несколько переменных параллельно с синтаксисом вида:
a, b: = 0, 1
который одновременно присваивает 0 a
и 1 b
. Это чаще всего известно как параллельное присвоение ; он был введен в CPL в 1963 году под названием одновременное присвоение и иногда называется множественное присвоение, хотя это сбивает с толку при использовании с «одиночным назначением», поскольку это не противоположности. Если правая часть присвоения представляет собой единственную переменную (например, массив или структуру), функция называется распаковкой или деструктуризацией присвоения :
var list: = {0, 1} a, b: = list
Список будет распакован так, что 0 будет присвоен a
, а 1 - b
. Кроме того,
a, b: = b, a
меняет местами значения a
и b
. В языках без параллельного присвоения это должно быть написано для использования временной переменной
var t: = a a: = b b: = t
, поскольку a: = b; b: = a
оставляет и a
, и b
с исходным значением b
.
. Некоторые языки, такие как Go и Python, сочетают параллельное присваивание, кортежи и автоматическое распаковка кортежа, чтобы разрешить несколько возвращаемых значений из одной функции, как в этом примере Python,
def f (): return 1, 2 a, b = f ()
в то время как другие языки, такие как поскольку C #, показанный здесь, требует явного построения и деконструкции кортежа в круглых скобках:
(a, b) = (b, a);
(строка, число) f () =>("foo", 1); var (a, b) = f ();
Это обеспечивает альтернативу использованию выходных параметров для возврата нескольких значений из функции. Это относится к CLU (1974), и CLU помогло популяризировать параллельное присваивание в целом.
C # дополнительно допускает обобщенное назначение деконструкции с реализацией, определяемой выражением в правой части, поскольку компилятор ищет соответствующий экземпляр или extension Деконструировать метод
для выражения, которое должно иметь выходные параметры для присваиваемых переменных. Например, один из таких методов, который дал бы классу , который проявляется в том же поведении, что и возвращаемое значение f ()
выше, будет
void Deconstruct (out string a, out int b) {a = "foo"; b = 1; }
В C и C ++ оператор запятой аналогичен параллельному присваиванию, позволяя выполнять несколько присваиваний в пределах одного оператора, записывая a = 1, b = 2
вместо а, b = 1, 2
. Это в основном используется в для циклов и заменяется параллельным присваиванием в других языках, таких как Go. Однако приведенный выше код C ++ не гарантирует идеальной одновременности, поскольку правая часть следующего кода a = b, b = a + 1
оценивается после левой стороны. В таких языках, как Python, a, b = b, a + 1
будет назначать две переменные одновременно, используя начальное значение a для вычисления нового b.
Использование знака равенства =
в качестве оператора присваивания часто подвергалось критике из-за конфликта с равенством в качестве сравнения для равенства. Это приводит как к замешательству новичков в написании кода, так и к замешательству даже у опытных программистов при чтении кода. Использование равенства для присваивания восходит к языку Хайнца Рутисхаузера Суперплан, разработанному с 1949 по 1951 год и особенно популяризированному Фортраном:
Печально известный пример плохого Идея заключалась в выборе знака равенства для обозначения присвоения. Он восходит к Фортрану в 1957 году и был слепо скопирован армиями разработчиков языков. Почему это плохая идея? Потому что это опровергает вековую традицию, когда «=» обозначает сравнение на равенство, предикат, который является либо истинным, либо ложным. Но в Фортране это означало назначение, обеспечение равенства. В этом случае операнды находятся в неравном положении: левый операнд (переменная) должен быть равен правому операнду (выражению). x = y не означает то же самое, что y = x.
— Никлаус Вирт, Хорошие идеи, в ЗазеркальеНачинающие программисты иногда путают назначение с оператором отношения для равенства, поскольку "=" означает равенство в математике и используется для присваивания во многих языках. Но присваивание изменяет значение переменной, а проверка на равенство проверяет, имеют ли два выражения одно и то же значение.
В некоторых языках, таких как BASIC, один знак равенства ("="
) используется как для оператора присваивания, так и для оператора отношения равенства с контекстом определение того, что имеется в виду. В других языках для двух операторов используются разные символы. Например:
": ="
), а оператор равенства - одинарное равенство ("="
)."="
), а оператор равенства представляет собой пару равных знаки ("=="
).<-
, как в x <- value
, но в определенных контекстах может использоваться единственный знак равенства.Сходство двух символов может привести к ошибкам, если программист забудет, в какой форме ("=
", "==
", ": =
") уместно, или ошибочно набирает "=
", когда предполагалось "==
". Это общая проблема программирования с такими языками, как C (включая одну известную попытку бэкдора в ядре Linux), где оператор присваивания также возвращает присвоенное значение (так же, как функция возвращает значение), и может быть правильно вложен в выражения. Если целью было сравнение двух значений в оператор if
, например, присвоение с большой вероятностью вернет значение, интерпретируемое как логическое значение true, и в этом случае будет выполнено предложение then
, что приведет к неожиданному поведению программы. Некоторые языковые процессоры (например, gcc ) могут обнаруживать такие ситуации и предупреждать программиста о потенциальной ошибке.
Двумя наиболее распространенными представлениями для копирующего присвоения являются знак равенства (=
) и двоеточие равно (: =
). Обе формы могут семантически обозначать либо оператор присваивания, либо оператор присваивания (который также имеет значение), в зависимости от языка и / или использования.
переменная = выражение | Fortran, PL / I, C (и потомки, такие как C ++, Java, и т. д.), оболочка Борна, Python, Go (присвоение предварительно объявленным переменным), R, PowerShell и т. д. |
переменная: = выражение | АЛГОЛ (и производные), Simula, CPL, BCPL, Pascal (и потомки, такие как Modula ), Мэри, PL / M, Ада, Smalltalk, Эйфель, Оберон, Дилан, Seed7, Python (выражение присваивания), Go (сокращение для объявления и определения переменной), Io, AMPL, ML,AutoHotkey и т. Д. |
Другие возможности включают стрелку влево или ключевое слово, хотя есть и другие, более редкие варианты:
переменная << expression | Magik |
переменная <- expression | F#, OCaml, R, S |
переменная <<- expression | R |
assign ("переменная", выражение) | R |
переменная ← выражение | APL, Smalltalk, BASIC Programming |
переменная =: выражение | J |
переменная LET = выражение | BASIC |
let variable: = expression | XQuery |
установить переменную в выражение | AppleScript |
установить переменную = выражение | оболочка C |
Set-Variable переменная (выражение) | PowerShell |
переменная: выражение | Macsyma, Maxima, Rebol, K |
var переменная выражение | язык сценариев mIRC |
ссылочная-переменная: - ссылочное-выражение | Simula |
математический присвоения псевдокода обычно обозначаются стрелкой влево.
Некоторые платформы помещают выражение слева и переменную справа:
MOVE выражение TO переменная | COBOL |
выражение → переменная | TI-BASIC, Casio BASIC |
выражение ->переменная | POP-2, BETA, R |
поместить выражение в переменную | LiveCode |
Некоторые языки, ориентированные на выражения, такие как Lisp и Tcl единообразно используют префиксный (или постфиксный) синтаксис для всех операторов, включая присваивание.
(setf variable expression) | Common Lisp |
(set! Variable expression) | Схема |
установить выражение переменной | Tcl |
выражение переменной! | Forth |