В компьютерном программировании оператор присваивания устанавливает и/или переустанавливает значение , хранящееся в ячейках хранения, обозначенных именем переменной ; другими словами, он копирует значение в переменную. В большинстве императивных языков программирования оператор присваивания (или выражение) является фундаментальной конструкцией.
Сегодня наиболее часто используемая нотация для этой операции (первоначально Superplan 1949–51, популяризированная Fortran 1957 и C ). Второе наиболее часто используемое обозначение — [1] (первоначально АЛГОЛ 1958 года, популяризированный Паскалем ). [2] Также используются многие другие обозначения. В некоторых языках используемый символ рассматривается как оператор (это означает, что оператор присваивания в целом возвращает значение). Другие языки определяют присваивание как оператор (это означает, что его нельзя использовать в выражении).x = expr
x := expr
Присвоения обычно позволяют переменной хранить разные значения в разное время в течение ее жизненного цикла и области действия . Однако некоторые языки (в первую очередь строго функциональные ) не допускают такого рода «деструктивного» переназначения, поскольку оно может подразумевать изменения нелокального состояния. Цель состоит в том, чтобы обеспечить ссылочную прозрачность , то есть функции, которые не зависят от состояния некоторых переменных, но дают одинаковые результаты для заданного набора параметрических входных данных в любой момент времени. Современные программы на других языках также часто используют аналогичные стратегии, хотя и менее строгие, и только в определенных частях, чтобы уменьшить сложность, обычно в сочетании с дополняющими методологиями, такими как структурирование данных , структурированное программирование и объектная ориентация .
Операция присваивания — это процесс в императивном программировании , в котором с течением времени с определенным именем переменной связываются разные значения. [1] Программа в такой модели работает, изменяя свое состояние с помощью последовательных операторов присваивания. [2] [3] Примитивы императивных языков программирования полагаются на присваивание для выполнения итерации . [4] На самом низком уровне присвоение реализуется с помощью машинных операций, таких как MOVE
или STORE
. [2] [4]
Переменные — это контейнеры для значений. В переменную можно поместить значение, а затем заменить его новым. Операция присваивания изменяет текущее состояние исполняемой программы. [3] Следовательно, назначение зависит от концепции переменных . В задании:
expression
в текущем состоянии программы.variable
присваивается вычисленное значение, заменяющее предыдущее значение этой переменной.Пример. Предполагая, что a
это числовая переменная, присвоение a := 2*a
означает, что содержимое переменной a
удваивается после выполнения оператора.
Пример сегмента кода C :
интервал х = 10 ; плавать у ; х = 23 ; у = 32,4f ;
В этом примере переменная x
сначала объявляется как int, а затем ей присваивается значение 10. Обратите внимание, что объявление и присвоение происходят в одном операторе. Во второй строке y
объявляется без присвоения. В третьей строке x
переназначается значение 23. Наконец, y
присваивается значение 32,4.
Для операции присваивания необходимо, чтобы значение expression
было четко определено (это допустимое значение rvalue ) и чтобы оно variable
представляло изменяемый объект (это допустимое изменяемое (неконстантное ) значение lvalue ). В некоторых языках, обычно динамических , нет необходимости объявлять переменную перед присвоением ей значения. В таких языках переменная автоматически объявляется при первом присвоении, а область ее объявления варьируется в зависимости от языка.
Любое присваивание, изменяющее существующее значение (например, x := x + 1
), запрещено в чисто функциональных языках. [4] В функциональном программировании присваивание не рекомендуется в пользу одиночного присваивания, более известного как инициализация . Одиночное присвоение является примером привязки имени и отличается от присвоения, описанного в этой статье, тем, что его можно выполнить только один раз, обычно при создании переменной; последующее переназначение не допускается.
Вычисление выражения не имеет побочного эффекта , если оно не меняет наблюдаемое состояние машины [5] , кроме получения результата, и всегда выдает одно и то же значение для одного и того же ввода. [4] Императивное присваивание может привести к побочным эффектам при уничтожении и недоступности старого значения при замене его новым, [6] и по этой причине в LISP и функциональном программировании называется деструктивным присваиванием , аналогично деструктивному обновлению .
Одиночное присваивание — единственная форма присваивания, доступная в чисто функциональных языках, таких как Haskell , которые не имеют переменных в смысле императивных языков программирования [4], а скорее называют константные значения, возможно, составной природы, с их элементами, постепенно определяемыми на основе спрос , для ленивых языков. Чисто функциональные языки могут предоставить возможность выполнять вычисления параллельно , избегая узкого места фон Неймана, связанного с последовательным выполнением по одному шагу, поскольку значения независимы друг от друга. [7]
Нечистые функциональные языки обеспечивают как одиночное, так и истинное присваивание (хотя истинное присваивание обычно используется реже, чем в императивных языках программирования). Например, в Scheme для всех переменных можно использовать как одиночное присвоение (с let
), так и истинное присвоение (с set!
), а для разрушительного обновления внутри списков, векторов, строк и т. д. предусмотрены специализированные примитивы. В OCaml разрешено только одиночное присвоение. для переменных — через синтаксис; однако деструктивное обновление может использоваться для элементов массивов и строк с отдельным оператором, а также для полей записей и объектов, которые были явно объявлены изменяемыми (то есть способными быть изменены после их первоначального объявления) программистом.let name = value
<-
Языки функционального программирования, использующие одно присваивание, включают Clojure (для структур данных, а не переменных), Erlang (он допускает множественное присваивание, если значения равны, в отличие от Haskell), F# , Haskell , JavaScript (для констант), Lava , OCaml , Oz (для переменных потока данных, а не ячеек), Racket (для некоторых структур данных, таких как списки, а не символы), SASL , Scala (для значений), SISAL , Standard ML . Код Пролога без возврата можно рассматривать как явное однократное присвоение, явное в том смысле, что его (именованные) переменные могут находиться в явно неназначенном состоянии или быть установлены ровно один раз. В Haskell, напротив, не может быть неназначенных переменных, и каждую переменную можно рассматривать как неявно установленную при ее создании в свое значение (или, скорее, в вычислительный объект, который будет выдавать свое значение по требованию ).
В некоторых языках программирования оператор присваивания возвращает значение, а в других — нет.
В большинстве языков программирования, ориентированных на выражения (например, C ), оператор присваивания возвращает присвоенное значение, что позволяет использовать такие идиомы, как x = y = a
, в которых оператор присваивания y = a
возвращает значение a
, которое затем присваивается x
. В таком операторе, как , возвращаемое значение функции используется для управления циклом при присвоении того же значения переменной.while ((ch = getchar()) != EOF) {…}
В других языках программирования, например в Scheme , возвращаемое значение присваивания не определено, и такие идиомы недопустимы.
В Haskell [8] нет присваивания переменных; но операции, подобные присваиванию (например, присвоение полю массива или полю изменяемой структуры данных), обычно оцениваются по типу единицы , который представлен как ()
. Этот тип имеет только одно возможное значение и поэтому не содержит никакой информации. Обычно это тип выражения, который оценивается исключительно из-за его побочных эффектов.
Некоторые шаблоны использования очень распространены и поэтому часто имеют специальный синтаксис для их поддержки. В первую очередь это синтаксический сахар , позволяющий уменьшить избыточность исходного кода, но он также помогает читателям кода понять намерения программиста и дает компилятору ключ к возможной оптимизации.
Случай, когда присвоенное значение зависит от предыдущего, настолько распространен, что многие императивные языки, особенно C и большинство его потомков, предоставляют специальные операторы, называемые расширенным присваиванием , например *=
, поэтому a = 2*a
вместо этого их можно записать как a *= 2
. [3] Помимо синтаксического сахара, это помогает компилятору, показывая, что модификация переменной на месте a
возможна.
Оператор Like 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
. [9] По сути, это эквивалентно тому tmp = f(); i = tmp; arr[i] = tmp
, что для временного значения не создается фактическая переменная.
Некоторые языки программирования, такие как APL , Common Lisp , [10] Go , [11] JavaScript (начиная с версии 1.7), PHP , Maple , Lua , occam 2 , [12] Perl , [13] Python , [14] REBOL , Ruby. , [15] и PowerShell позволяют назначать несколько переменных параллельно, используя такой синтаксис:
а, б := 0, 1
который одновременно присваивает 0 a
и 1 b
. Чаще всего это называют параллельным присваиванием ; оно было введено в CPL в 1963 году под названием « одновременное присвоение » [16] и иногда называется множественным присвоением , хотя это сбивает с толку при использовании с «одиночным присвоением», поскольку это не противоположности. Если правая часть присваивания представляет собой одну переменную (например, массив или структуру), эта функция называется распаковкой [17] или деструктуризацией присваивания : [18]
список переменных := {0, 1}а, б := список
Список будет распакован так, что 0 будет присвоено a
, а 1 — b
. Более того,
а, б := б, а
меняет местами значения a
и b
. В языках без параллельного присваивания это пришлось бы писать с использованием временной переменной.
вар т := аа := бб := т
поскольку a := b; b := a
оставляет оба a
и b
с исходным значением b
.
Некоторые языки, такие как Go , F# и Python , сочетают в себе параллельное присваивание, кортежи и автоматическую распаковку кортежей, чтобы обеспечить возможность возврата нескольких значений из одной функции, как в этом примере Python:
def f (): вернуть 1 , 2 a , b = f ()
в то время как другие языки, такие как C# и Rust , показанные здесь, требуют явного построения и деконструкции кортежей с помощью круглых скобок:
// Допустимый синтаксис C# или Rust ( a , b ) = ( b , a );
// Возврат кортежа C# ( string , int ) f () => ( "foo" , 1 ); вар ( а , б ) = ж ();
// Возвращаем кортеж Rust let f = || ( «фу» , 1 ); пусть ( a , b ) = f ();
Это обеспечивает альтернативу использованию выходных параметров для возврата нескольких значений из функции. Это относится к CLU (1974), и CLU помог популяризировать параллельное задание в целом.
C# дополнительно допускает обобщенное присвоение деконструкции с реализацией, определяемой выражением в правой части, поскольку компилятор ищет подходящий экземпляр или метод расширения Deconstruct
выражения, которое должно иметь выходные параметры для присваиваемых переменных. [19] Например, одним из таких методов, который придаст классу то же поведение, что и возвращаемое значение f()
выше, будет
void Deconstruct ( out string a , out int b ) { a = "foo" ; б = 1 ; }
В C и C++ оператор запятая аналогичен параллельному присваиванию, позволяя выполнять несколько присваиваний в одном операторе, записывая a = 1, b = 2
вместо a, b = 1, 2
. В основном это используется в циклах for и заменяется параллельным присваиванием в других языках, таких как Go. [20]
Однако приведенный выше код C++ не обеспечивает идеальную одновременность, поскольку правая часть следующего кода a = b, b = a+1
вычисляется после левой части. В таких языках, как Python, a, b = b, a+1
обе переменные присваиваются одновременно, используя начальное значение a для вычисления нового b.
Использование знака равенства =
в качестве оператора присваивания часто подвергалось критике из-за конфликта с равенством при сравнении на равенство. Это приводит как к путанице у новичков при написании кода, так и к путанице даже у опытных программистов при чтении кода. Использование равенства для присваивания восходит к языку Superplan Хайнца Рутисхаузера , разработанному с 1949 по 1951 год, и было особенно популяризировано Фортраном:
Ярким примером плохой идеи был выбор знака равенства для обозначения присваивания. Он восходит к Фортрану в 1957 году [a] и был слепо скопирован армиями разработчиков языка. Почему это плохая идея? Потому что это опровергает вековую традицию, позволяющую «=» обозначать сравнение на равенство, предикат, который может быть либо истинным, либо ложным. Но в Фортране это означало присвоение, обеспечение равенства. В этом случае операнды находятся в неравных отношениях: левый операнд (переменная) должен быть равен правому операнду (выражению). x = y не означает то же самое, что y = x. [21]
— Никлаус Вирт , «Хорошие идеи в Зазеркалье»
Начинающие программисты иногда путают присваивание с оператором сравнения равенства, поскольку "=" означает равенство в математике и используется для присваивания во многих языках. Но присваивание изменяет значение переменной, а проверка на равенство проверяет, имеют ли два выражения одинаковое значение.
В некоторых языках, таких как BASIC , один знак равенства ( "="
) используется как для оператора присваивания, так и для оператора сравнения равенства, при этом контекст определяет, какой из них имеется в виду. В других языках для этих двух операторов используются разные символы. [22] Например:
":="
), а оператор равенства — одиночное равенство ( "="
)."="
), а оператор равенства — пару знаков равенства ( "=="
).<-
, как и в x <- value
, но в определенных контекстах можно использовать один знак равенства.Сходство двух символов может привести к ошибкам, если программист забудет, какая форма (" =
", " ==
", " :=
") подходит, или неправильно напечатает " =
", когда " ==
" предполагалось. Это распространенная проблема программирования на таких языках, как C (включая одну известную попытку взлома ядра Linux), [23] где оператор присваивания также возвращает назначенное значение (так же, как функция возвращает значение), и может быть правильно вложенными внутри выражений. if
Например, если намерением было сравнить два значения в операторе, то присваивание вполне вероятно вернет значение, интерпретируемое как логическое значение true, и в этом случае then
предложение будет выполнено, что приведет к неожиданному поведению программы. Некоторые языковые процессоры (например, gcc ) могут обнаруживать такие ситуации и предупреждать программиста о потенциальной ошибке. [24] [25]
Двумя наиболее распространенными представлениями присваивания копирования являются знак равенства ( =
) и двоеточие-равно ( :=
). Обе формы могут семантически обозначать либо оператор присваивания , либо оператор присваивания (который также имеет значение), в зависимости от языка и/или использования.
Другие возможности включают стрелку влево или ключевое слово, хотя есть и другие, более редкие варианты:
Математические назначения псевдокодов обычно обозначаются стрелкой влево.
Некоторые платформы помещают выражение слева, а переменную справа:
Некоторые языки, ориентированные на выражения, такие как Lisp [34] [35] и Tcl, единообразно используют префиксный (или постфиксный) синтаксис для всех операторов, включая присваивание.
=
предшествовало Фортрану, хотя Фортран популяризировал его.