Задание (информатика)
В компьютерном программировании оператор присваивания устанавливает и/или переустанавливает значение , хранящееся в ячейках хранения, обозначенных переменной именем ; другими словами, он копирует значение в переменную. В большинстве императивных языков программирования оператор присваивания (или выражение) является фундаментальной конструкцией.
Сегодня наиболее часто используемое обозначение для этой операции: x = expr
(первоначально Superplan 1949–51, популяризированный Fortran 1957 и C ). Вторым наиболее часто используемым обозначением является [1] x := expr
(первоначально АЛГОЛ 1958 года, популяризированный Паскалем ). [2] Также используются многие другие обозначения. В некоторых языках используемый символ рассматривается как оператор (это означает, что оператор присваивания в целом возвращает значение). Другие языки определяют присваивание как оператор (это означает, что его нельзя использовать в выражении).
Присвоения обычно позволяют переменной хранить разные значения в разное время в течение ее жизненного цикла и области действия . Однако некоторые языки (в первую очередь строго функциональные ) не допускают такого рода «деструктивного» переназначения, поскольку оно может подразумевать изменения нелокального состояния. Цель состоит в том, чтобы обеспечить ссылочную прозрачность , то есть функции, которые не зависят от состояния некоторых переменных, но дают одинаковые результаты для заданного набора параметрических входных данных в любой момент времени. Современные программы на других языках также часто используют аналогичные стратегии, хотя и менее строгие, и только в определенных частях, чтобы уменьшить сложность, обычно в сочетании с дополняющими методологиями, такими как структурирование данных , структурированное программирование и объектная ориентация .
Семантика
[ редактировать ]Операция присваивания — это процесс в императивном программировании , в котором с течением времени с определенным именем переменной связываются разные значения. [1] Программа в такой модели работает, изменяя свое состояние с помощью последовательных операторов присваивания. [2] [3] Примитивы императивных языков программирования полагаются на присваивание для выполнения итерации . [4] На самом низком уровне присвоение реализуется с помощью машинных операций, таких как MOVE
или STORE
. [2] [4]
Переменные — это контейнеры для значений. В переменную можно поместить значение, а затем заменить его новым. Операция присваивания изменяет текущее состояние исполняемой программы. [3] Следовательно, присвоение зависит от концепции переменных . В задании:
- The
expression
оценивается в текущем состоянии программы. - The
variable
присваивается вычисленное значение, заменяющее предыдущее значение этой переменной.
Пример: Предполагая, что a
— числовая переменная, присвоение a := 2*a
означает, что содержимое переменной a
удваивается после выполнения оператора.
Пример сегмента кода C :
int x = 10;
float y;
x = 23;
y = 32.4f;
В этом примере переменная x
сначала объявляется как целое число, а затем ему присваивается значение 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 , возвращаемое значение присваивания не определено, и такие идиомы недопустимы.
В Хаскеле [8] нет присваивания переменных; но операции, подобные присваиванию (например, присвоение полю массива или полю изменяемой структуры данных), обычно оцениваются по типу единицы , который представлен как ()
. Этот тип имеет только одно возможное значение и поэтому не содержит никакой информации. Обычно это тип выражения, который оценивается исключительно из-за его побочных эффектов.
Варианты форм поручения
[ редактировать ]Некоторые шаблоны использования очень распространены и поэтому часто имеют специальный синтаксис для их поддержки. В первую очередь это синтаксический сахар, позволяющий уменьшить избыточность исходного кода, но он также помогает читателям кода понять замысел программиста и дает компилятору ключ к возможной оптимизации.
Дополненное задание
[ редактировать ]Случай, когда присвоенное значение зависит от предыдущего, настолько распространен, что многие императивные языки, особенно C и большинство его потомков, предоставляют специальные операторы, называемые расширенным присваиванием , например *=
, так a = 2*a
вместо этого можно записать как a *= 2
. [3] Помимо синтаксического сахара, это помогает компилятору, ясно давая понять, что модификация переменной на месте 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
. [9] По сути это эквивалентно tmp = f(); i = tmp; arr[i] = tmp
хотя для временного значения не создается фактическая переменная.
Параллельное назначение
[ редактировать ]Некоторые языки программирования, такие как APL , Common Lisp , [10] Идти , [11] JavaScript (начиная с версии 1.7), PHP , Maple , Lua , occam 2 , [12] Перл , [13] Питон , [14] РЕБОЛ , Руби , [15] и PowerShell позволяют назначать несколько переменных параллельно, используя такой синтаксис:
a, b := 0, 1
который одновременно присваивает 0 a
и от 1 до b
. Чаще всего это называют параллельным присваиванием ; оно было введено в CPL в 1963 году под названием « Совместное назначение» . [16] и иногда его называют множественным присвоением , хотя это сбивает с толку при использовании с «одиночным присвоением», поскольку это не противоположности. Если правая часть присваивания представляет собой одну переменную (например, массив или структуру), эта функция называется распаковкой. [17] или деструктурирующее задание : [18]
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 , F# и Python , сочетают параллельное присваивание, кортежи и автоматическую распаковку кортежей , чтобы обеспечить возможность возврата нескольких значений из одной функции, как в этом примере Python:
def f():
return 1, 2
a, b = f()
в то время как другие языки, такие как C# и Rust , показанные здесь, требуют явного построения и деконструкции кортежей с помощью круглых скобок:
// Valid C# or Rust syntax
(a, b) = (b, a);
// C# tuple return
(string, int) f() => ("foo", 1);
var (a, b) = f();
// Rust tuple return
let f = || ("foo", 1);
let (a, b) = f();
Это обеспечивает альтернативу использованию выходных параметров для возврата нескольких значений из функции. Это относится к CLU (1974), и CLU помог популяризировать параллельное задание в целом.
C# дополнительно допускает обобщенное назначение деконструкции с реализацией, определяемой выражением в правой части, когда компилятор ищет подходящий экземпляр или расширение. Deconstruct
метод для выражения, которое должно иметь выходные параметры для присваиваемых переменных. [19] Например, один из таких методов, который бы придавал классу то же поведение, что и возвращаемое значение f()
выше было бы
void Deconstruct(out string a, out int b) { a = "foo"; b = 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 году. [а] и был слепо скопирован армиями языковых дизайнеров. Почему это плохая идея? Потому что это опровергает вековую традицию, позволяющую «=» обозначать сравнение на равенство, предикат, который может быть либо истинным, либо ложным. Но в Фортране это означало присвоение, обеспечение равенства. В этом случае операнды находятся в неравных отношениях: левый операнд (переменная) должен быть равен правому операнду (выражению). x = y не означает то же самое, что y = x. [21]
— Никлаус Вирт , «Хорошие идеи в Зазеркалье»
Начинающие программисты иногда путают присваивание с оператором сравнения , обозначающим равенство, поскольку «=" означает равенство в математике и используется для присваивания во многих языках. Но присваивание изменяет значение переменной, а проверка на равенство проверяет, имеют ли два выражения одинаковое значение.
В некоторых языках, таких как BASIC , один знак равенства ( "="
) используется как для оператора присваивания, так и для оператора отношения равенства, при этом контекст определяет, какой из них имеется в виду. В других языках для этих двух операторов используются разные символы. [22] Например:
- В Алголе и Паскале оператором присваивания является двоеточие и знак равенства (
":="
), в то время как оператор равенства представляет собой одиночное равенство ("="
). - В C оператор присваивания представляет собой один знак равенства (
"="
), а оператор равенства представляет собой пару знаков равенства ("=="
). - В R оператор присваивания в основном
<-
, как вx <- value
, но в определенных контекстах можно использовать один знак равенства.
Сходство двух символов может привести к ошибкам, если программист забудет, в какой форме (" =
", " ==
", " :=
") подходит, или опечатка " =
" когда " ==
". Это распространенная проблема программирования на таких языках, как C (включая одну известную попытку взлома ядра Linux), [23] где оператор присваивания также возвращает присвоенное значение (так же, как функция возвращает значение) и может быть правильно вложен внутри выражений. Если намерением было сравнить два значения в if
например, присваивание вполне вероятно вернет значение, интерпретируемое как логическое значение true, и в этом случае then
будет выполнено, что приведет к неожиданному поведению программы. Некоторые языковые процессоры (например, gcc ) могут обнаруживать такие ситуации и предупреждать программиста о потенциальной ошибке. [24] [25]
Обозначения
[ редактировать ]Двумя наиболее распространенными представлениями присваивания копирования являются знак равенства ( =
) и двоеточие равно ( :=
). Обе формы могут семантически обозначать либо оператор присваивания присваивания, либо оператор (который также имеет значение), в зависимости от языка и/или использования.
variable = expression
Fortran , PL/I , C (и его потомки, такие как C++ , Java и т. д.), оболочка Bourne , Python , Go (присвоение предварительно объявленным переменным), R , PowerShell , Nim и т. д. variable := expression
АЛГОЛ (и производные), Simulate , CPL , BCPL , Pascal [26] (и потомки, такие как Modula ), Mary , PL/M , Ada , Smalltalk , Eiffel , [27] [28] Оберон , Дилан , [29] Seed7 , Python (выражение присваивания), [30] Go (сокращение от объявления и определения переменной), [31] Io , AMPL , ML (присваивание эталонному значению), [32] Автохоткей и т. д.
Другие возможности включают стрелку влево или ключевое слово, хотя есть и другие, более редкие варианты:
variable << expression
Магия variable <- expression
Ф# , ОКамл , Р , С variable <<- expression
Р assign("variable", expression)
Р variable ← expression
АПЛ , [33] Smalltalk , Программирование на языке BASIC variable =: expression
Дж LET variable = expression
БАЗОВЫЙ let variable := expression
XQuery set variable to expression
AppleScript set variable = expression
оболочка C Set-Variable variable (expression)
PowerShell variable : expression
Максима, Максима , К variable: expression
Ребол var variable expression
язык сценариев mIRC reference-variable :- reference-expression
начало
Математические назначения псевдокодов обычно изображаются стрелкой влево.
Некоторые платформы помещают выражение слева, а переменную справа:
MOVE expression TO variable
КОБОЛ expression → variable
ТИ-БЕЙСИК , Касио БЕЙСИК expression -> variable
ПОП-2 , БЕТА , Р put expression into variable
LiveCode PUT expression IN variable
АВС
Некоторые языки, ориентированные на выражения, такие как Lisp. [34] [35] и Tcl единообразно используют префиксный (или постфиксный) синтаксис для всех операторов, включая присваивание.
(setf variable expression)
Общий Лисп (set! variable expression)
Схема [36] [37] [38] set variable expression
Ткл expression variable !
Форт
См. также
[ редактировать ]Примечания
[ редактировать ]- ^ Использование
=
предшествует Фортрану, хотя он был популяризирован Фортраном.
Ссылки
[ редактировать ]- ^ Перейти обратно: а б «2cs24 Декларативный» . www.csc.liv.ac.uk. Архивировано из оригинала 24 апреля 2006 года . Проверено 20 апреля 2018 г.
- ^ Перейти обратно: а б с «Императивное программирование» . уа.еду . Архивировано из оригинала 4 марта 2016 года . Проверено 20 апреля 2018 г.
- ^ Перейти обратно: а б с Рюдигер-Маркус Флейг (2008). Программирование биоинформатики на Python: практический курс для начинающих . Вайли-ВЧ. стр. 98–99. ISBN 978-3-527-32094-3 . Проверено 25 декабря 2010 г.
- ^ Перейти обратно: а б с д и Пересекая границы: изучайте функциональное программирование с помощью Haskell. Архивировано 19 ноября 2010 г., в Wayback Machine . Брюсом Тейтом,
- ^ Митчелл, Джон К. (2003). Концепции в языках программирования . Издательство Кембриджского университета. п. 23. ISBN 978-0-521-78098-8 . Проверено 3 января 2011 г.
- ^ «Императивные языки программирования (IPL)» (PDF) . gwu.edu . Архивировано из оригинала (PDF) 16 июля 2011 г. Проверено 20 апреля 2018 г.
- ^ Джон К. Митчелл (2003). Концепции в языках программирования . Издательство Кембриджского университета. стр. 81–82. ISBN 978-0-521-78098-8 . Проверено 3 января 2011 г.
- ^ Худак, Пол (2000). Школа экспрессии Haskell: изучение функционального программирования с помощью мультимедиа . Кембридж: Издательство Кембриджского университета. ISBN 0-521-64408-9 .
- ^ «7. Простые операторы — документация Python 3.6.5» . docs.python.org . Проверено 20 апреля 2018 г.
- ^ «CLHS: Макрос SETF, PSETF» . Общая гиперспецификация Lisp . Лиспворкс . Проверено 23 апреля 2019 г.
- ^ Спецификация языка программирования Go: Задания
- ^ ИНМОС Лимитед, изд. (1988). Справочное руководство Оккам 2 . Нью-Джерси: Прентис Холл. ISBN 0-13-629312-3 .
- ^ Уолл, Ларри ; Кристиансен, Том; Шварц, Рэндал К. (1996). Язык программирования Perl (2-е изд.). Кембридж: О'Рейли. ISBN 1-56592-149-6 .
- ^ Лутц, Марк (2001). Язык программирования Python (2-е изд.). Севастополь: О'Рейли. ISBN 0-596-00085-5 .
- ^ Томас, Дэвид; Хант, Эндрю (2001). Программирование на Ruby: Руководство прагматичного программиста . Река Аппер-Седл: Эддисон Уэсли. ISBN 0-201-71089-7 .
- ^ Д.В. Бэррон и др. , «Основные особенности CPL», Computer Journal 6 :2:140 (1963). полный текст (подписка)
- ^ «PEP 3132 — Расширенная итеративная распаковка» . Legacy.python.org . Проверено 20 апреля 2018 г.
- ^ «Деструктуризация задания» . Веб-документы MDN . Проверено 20 апреля 2018 г.
- ^ «Деконструкция кортежей и других типов» . Документы Майкрософт . Майкрософт . Проверено 29 августа 2019 г.
- ^ Эффективный ход : для , «Наконец, в Go нет оператора-запятой, а ++ и -- являются операторами, а не выражениями. Таким образом, если вы хотите запустить несколько переменных в for, вам следует использовать параллельное присваивание (хотя это исключает ++ и --)».
- ^ Никлаус Вирт. «Хорошие идеи в Зазеркалье» . CiteSeerX 10.1.1.88.8309 .
- ^ «Язык программирования C++. Основы» . ntu.edu.sg. 01.06.2013 . Проверено 21 июня 2024 г.
- ^ Корбет (6 ноября 2003 г.). «Попытка взлома ядра» . lwn.net . Проверено 21 июня 2024 г.
- ^ «Параметры статического анализатора (с использованием коллекции компиляторов GNU (GCC))» . gcc.gnu.org . Проверено 21 июня 2024 г.
- ^ Дейтел, Пол; Дейтел, Харви (25 октября 2022 г.). «Управляющие операторы C++, часть 2» . Домашние задания . Проверено 21 июня 2024 г.
- ^ Мур, Лори (1980). Основы программирования на языке Паскаль . Нью-Йорк: Джон Уайли и сыновья. ISBN 0-470-26939-1 .
- ^ Мейер, Бертран (1992). Эйфель Язык . Хемел Хемпстед: Prentice Hall International (Великобритания). ISBN 0-13-247925-7 .
- ^ Винер, Ричард (1996). Объектно-ориентированное введение в информатику с использованием Eiffel . Река Аппер-Сэддл, Нью-Джерси: Прентис-Холл. ISBN 0-13-183872-5 .
- ^ Фейнберг, Нил; Кин, Соня Э.; Мэтьюз, Роберт О.; Витингтон, П. Такер (1997). Программирование Дилана . Массачусетс: Эддисон Уэсли. ISBN 0-201-47976-1 .
- ^ «PEP 572 – Выражения присваивания» . python.org . 28 февраля 2018 года . Проверено 4 марта 2020 г.
- ^ «Спецификация языка программирования Go — Язык программирования Go» . golang.org . Проверено 20 апреля 2018 г.
- ^ Уллман, Джеффри Д. (1998). Элементы программирования машинного обучения: издание ML97 . Энглвуд Клиффс, Нью-Джерси: Прентис Холл. ISBN 0-13-790387-1 .
- ^ Айверсон, Кеннет Э. (1962). Язык программирования . Джон Уайли и сыновья. ISBN 0-471-43014-5 . Архивировано из оригинала 4 июня 2009 г. Проверено 9 мая 2010 г.
- ^ Грэм, Пол (1996). ANSI Common Lisp . Нью-Джерси: Прентис Холл. ISBN 0-13-370875-6 .
- ^ Стил, Гай Л. (1990). Common Lisp: язык . Лексингтон: Цифровая пресса. ISBN 1-55558-041-6 .
- ^ Дибвиг, Р. Кент (1996). Язык программирования Scheme: ANSI Scheme . Нью-Джерси: Прентис Холл. ISBN 0-13-454646-6 .
- ^ Смит, Джерри Д. (1988). Знакомство со схемой . Нью-Джерси: Прентис Холл. ISBN 0-13-496712-7 .
- ^ Абельсон, Гарольд; Сассман, Джеральд Джей; Сассман, Джули (1996). Структура и интерпретация компьютерных программ . Нью-Джерси: МакГроу-Хилл. ISBN 0-07-000484-6 .