Из Википедии, бесплатной энциклопедии
Перейти к навигации Перейти к поиску

В компьютерном программировании оператор присваивания устанавливает и / или повторно устанавливает значение, хранящееся в ячейке (ах) памяти, обозначенной именем переменной ; другими словами, он копирует значение в переменную. В большинстве императивных языков программирования оператор присваивания (или выражение) является фундаментальной конструкцией.

Сегодня наиболее часто используемым обозначением для этой базовой операции стало (первоначально Superplan 1949–51, популяризированный Fortran 1957 и C ), за которым следует [1] (первоначально ALGOL 1958, популяризированный Паскалем ) [2], хотя есть многие другие используемые обозначения. В некоторых языках используемый символ рассматривается как оператор (что означает, что оператор присваивания в целом возвращает значение), в то время как другие определяют присвоение как оператор (что означает, что его нельзя использовать в выражении).x = expr x := expr

Присваивания обычно позволяют переменной хранить разные значения в разное время в течение ее жизненного цикла и области действия . Однако некоторые языки (в основном строго функциональные ) не допускают такого «деструктивного» переназначения, так как это может означать изменения нелокального состояния. Цель состоит в том, чтобы обеспечить ссылочную прозрачность , то есть функции, которые не зависят от состояния некоторой переменной (переменных), но дают одинаковые результаты для заданного набора параметрических входных данных в любой момент времени. Современные программы на других языках также часто используют аналогичные стратегии, хотя и менее строгие, и только в определенных частях, чтобы уменьшить сложность, обычно в сочетании с дополнительными методологиями, такими как структурирование данных ,структурное программирование и объектная ориентация .

Семантика [ править ]

Операция присваивания - это процесс в императивном программировании, в котором различные значения связываются с конкретным именем переменной по прошествии времени. [1] Программа в такой модели работает, изменяя свое состояние, используя последовательные операторы присваивания. [2] [3] Примитивы императивных языков программирования полагаются на присваивание для выполнения итерации . [4] На самом низком уровне назначение реализуется с помощью машинных операций, таких как MOVEили STORE. [2] [4]

Переменные - это контейнеры для значений. Можно поместить значение в переменную, а затем заменить его новым. Операция присваивания изменяет текущее состояние исполняемой программы. [3] Следовательно, присвоение зависит от концепции переменных . В задании:

  • expressionОценивается в текущем состоянии программы.
  • Объекту variableприсваивается вычисленное значение, заменяющее предыдущее значение этой переменной.

Пример: Предполагая, что aэто числовая переменная, присвоение a := 2*aозначает, что содержимое переменной aудваивается после выполнения оператора.

Пример сегмента кода C :

int  x  =  10 ;  float  y ; х  =  23 ; y  =  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 (для vals), SISAL , Standard ML . Non- возвратами Пролог код можно считать явнымоднократное присваивание, явное в том смысле, что его (именованные) переменные могут находиться в явно неназначенном состоянии или быть установлены ровно один раз. В Haskell, напротив, не может быть неназначенных переменных, и каждая переменная может рассматриваться как неявно установленная на свое значение (или, скорее, на вычислительный объект, который будет генерировать свое значение по запросу ) при ее создании.

Ценность задания [ править ]

В некоторых языках программирования оператор присваивания возвращает значение, а в других - нет.

В большинстве языков программирования, ориентированных на выражения (например, C ), оператор присваивания возвращает присвоенное значение, допуская такие идиомы, как x = y = a, в которых оператор присваивания y = aвозвращает значение a, которое затем присваивается x. В таком операторе, как , например , возвращаемое значение функции используется для управления циклом при присвоении того же значения переменной.while ((ch = getchar()) != EOF) {}

В других языках программирования, например Scheme , возвращаемое значение присваивания не определено, и такие идиомы недействительны.

В Haskell , [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] 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}a, b: = список

Список будет распакован так, что 0 будет назначено, aа 1 - b. Более того,

а, б: = б, а

меняет местами значения aи b. В языках без параллельного присваивания это должно быть написано для использования временной переменной

var t: = aа: = бб: = т

поскольку a := b; b := aоставляет оба значения aи bс исходным значением b.

Некоторые языки, такие как Go и Python, объединяют параллельное присваивание, кортежи и автоматическую распаковку кортежей, чтобы разрешить несколько возвращаемых значений из одной функции, как в этом примере Python,

def  f ():  вернуть  1 ,  2 a ,  b  =  f ()

в то время как другие языки, такие как C # , показанный здесь, требуют явного построения и деконструкции кортежей с помощью круглых скобок:

(а, б) = (б, а);
( Строка ,  INT )  F ()  =>  ( "Foo" ,  1 ); var  ( 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.

Назначение против равенства [ править ]

Использование знака равенства =в качестве оператора присваивания часто подвергалось критике из-за конфликта с равенством в качестве сравнения на равенство. Это приводит как к замешательству новичков при написании кода, так и к замешательству даже у опытных программистов при чтении кода. Использование равных для дат назначения назад к Хайнц Ратишозер языка «s SUPERPLAN , разработанный с 1949 по 1951 год , и особенно популяризировал Fortran:

Печально известным примером плохой идеи был выбор знака равенства для обозначения назначения. Он восходит к Фортрану в 1957 году [a] и был слепо скопирован армиями разработчиков языков. Почему это плохая идея? Потому что это опровергает вековую традицию, когда «=» обозначает сравнение на равенство, предикат, который является либо истинным, либо ложным. Но в Фортране это означало назначение, обеспечение равенства. В этом случае операнды находятся в неравном положении: левый операнд (переменная) должен быть равен правому операнду (выражению). x = y не означает то же самое, что y = x. [21]

-  Никлаус Вирт , Хорошие идеи, в Зазеркалье

Начинающие программисты иногда путают присваивание с оператором отношения равенства, поскольку «=» означает равенство в математике и используется для присваивания во многих языках. Но присваивание изменяет значение переменной, а проверка на равенство проверяет, имеют ли два выражения одно и то же значение.

В некоторых языках, таких как BASIC , один знак равенства ( "=") используется как для оператора присваивания, так и для оператора отношения равенства, при этом контекст определяет, что имеется в виду. В других языках для двух операторов используются разные символы. Например:

  • В ALGOL и Pascal оператор присваивания представляет собой двоеточие и знак равенства ( ":="), в то время как оператор равенства представляет собой одиночное выражение equals ( "=").
  • В языке C оператор присваивания - это единственный знак равенства ( "="), а оператор равенства - это пара знаков равенства ( "==").
  • В R оператор присваивания в основном <-такой же, как в x <- value, но в определенных контекстах может использоваться единственный знак равенства.

Сходство двух символов может привести к ошибкам, если программист забудет, какая форма (" =", " ==", " :=") подходит, или опечатает " =", когда " ==" было задумано. Это обычная проблема программирования с такими языками, как C (включая одну известную попытку проникнуть в ядро ​​Linux) [22], где оператор присваивания также возвращает присвоенное значение (таким же образом, как функция возвращает значение) и может быть правильно вложенными внутри выражений. Если, например, намерение состояло в том, чтобы сравнить два значения в ifоператоре, присвоение с большой вероятностью вернет значение, интерпретируемое как логическое значение true, и в этом случае thenпредложение будет выполнено, что приведет к неожиданному поведению программы.Некоторые языковые процессоры (например,gcc ) может обнаруживать такие ситуации и предупреждать программиста о потенциальной ошибке.

Обозначение [ править ]

Двумя наиболее распространенными представлениями для копирования присваивания являются знак равенства ( =) и двоеточие равно ( :=). Обе формы могут семантически обозначают либо присваивания заявления или присвоение оператор (который также имеет значение), в зависимости от языка и / или использования.

Другие возможности включают стрелку влево или ключевое слово, хотя есть и другие, более редкие варианты:

Назначения математических псевдокодов обычно обозначаются стрелкой влево.

Некоторые платформы помещают выражение слева и переменную справа:

Некоторые языки, ориентированные на выражения, такие как Lisp [31] [32] и Tcl, единообразно используют префиксный (или постфиксный) синтаксис для всех операторов, включая присваивание.

См. Также [ править ]

  • Оператор присваивания в C ++
  • Оператор (программирование)
  • Привязка имени
  • Унификация (вычисления)
  • Неизменяемый объект
  • Const-правильность

Заметки [ править ]

  1. ^ Использование=предшествовавших Fortran, хотя это было популяризировано Fortran.

Ссылки [ править ]

  1. ^ a b "Декларативная 2cs24" . www.csc.liv.ac.uk . Архивировано из оригинала 24 апреля 2006 года . Проверено 20 апреля 2018 года .
  2. ^ a b c «Императивное программирование» . uah.edu . Проверено 20 апреля 2018 года .
  3. ^ a b c Рюдигер-Маркус Флейг (2008). Программирование биоинформатики на Python: практический курс для начинающих . Wiley-VCH. С. 98–99. ISBN 978-3-527-32094-3. Проверено 25 декабря 2010 года .
  4. ^ a b c d e Переходя границы: исследуйте функциональное программирование с помощью Haskell. Архивировано 19 ноября 2010 г. на Wayback Machine , Брюс Тейт.
  5. ^ Митчелл, Джон С. (2003). Понятия в языках программирования . Издательство Кембриджского университета. п. 23. ISBN 978-0-521-78098-8. Проверено 3 января 2011 года .
  6. ^ "Императивные языки программирования (IPL)" (PDF) . gwu.edu . Проверено 20 апреля 2018 года .
  7. ^ Джон С. Митчелл (2003). Понятия в языках программирования . Издательство Кембриджского университета. С. 81–82. ISBN 978-0-521-78098-8. Проверено 3 января 2011 года .
  8. ^ Худак, Пол (2000). Школа выражения Haskell: изучение функционального программирования с помощью мультимедиа . Кембридж: Издательство Кембриджского университета. ISBN 0-521-64408-9.
  9. ^ «7. Простые утверждения - документация Python 3.6.5» . docs.python.org . Проверено 20 апреля 2018 года .
  10. ^ "CLHS: Macro SETF, PSETF" . Common Lisp Hyperspec . LispWorks . Проверено 23 апреля 2019 года .
  11. ^ Спецификация языка программирования Go: Назначения
  12. ^ ИНМОС Лимитед, изд. (1988). Справочное руководство Occam 2 . Нью-Джерси: Прентис-Холл. ISBN 0-13-629312-3.
  13. ^ Уолл, Ларри ; Кристиансен, Том; Шварц, Рэндал С. (1996). Язык программирования Perl (2-е изд.). Кембридж: О'Рейли. ISBN 1-56592-149-6.
  14. Перейти ↑ Lutz, Mark (2001). Язык программирования Python (2-е изд.). Севастополь: О'Рейли. ISBN 0-596-00085-5.
  15. ^ Томас, Дэвид; Хант, Эндрю (2001). Программирование на Ruby: Руководство программиста-прагматика . Река Аппер Сэдл: Эддисон Уэсли. ISBN 0-201-71089-7.
  16. ^ DW Barron et al. , "Основные особенности CPL", Компьютерный журнал 6 : 2: 140 (1963). полный текст (подписка)
  17. ^ «PEP 3132 - Расширенная итерационная распаковка» . legacy.python.org . Проверено 20 апреля 2018 года .
  18. ^ «Деструктурирующее задание» . Веб-документы MDN . Проверено 20 апреля 2018 года .
  19. ^ «Деконструкция кортежей и других типов» . Документы Microsoft . Microsoft . Проверено 29 августа 2019 .
  20. ^ Эффективный Go : для «Наконец, в Go нет оператора запятой, а ++ и - являются операторами, а не выражениями. Таким образом, если вы хотите запустить несколько переменных в for, вам следует использовать параллельное присваивание (хотя это исключает ++ и - ). "
  21. Никлаус Вирт. «Хорошие идеи в Зазеркалье». CiteSeerX 10.1.1.88.8309 .  Отсутствует или пусто |url=( справка )
  22. Corbet (6 ноября 2003 г.). «Попытка взломать ядро» .
  23. ^ Мур, Лори (1980). Основы программирования на Паскале . Нью-Йорк: Джон Вили и сыновья. ISBN 0-470-26939-1.
  24. ^ Мейер, Бертран (1992). Эйфель-язык . Хемел Хемпстед: Prentice Hall International (Великобритания). ISBN 0-13-247925-7.
  25. ^ Винер, Ричард (1996). Объектно-ориентированное введение в информатику с использованием Eiffel . Река Аппер Сэдл, Нью-Джерси: Prentice Hall. ISBN 0-13-183872-5.
  26. ^ Файнберг, Нил; Кин, Соня Э .; Мэтьюз, Роберт О .; Витингтон, П. Такер (1997). Дилан Программирование . Массачусетс: Эддисон Уэсли. ISBN 0-201-47976-1.
  27. ^ «PEP 572 - Выражения присваивания» . python.org . 28 февраля 2018 . Дата обращения 4 марта 2020 .
  28. ^ «Спецификация языка программирования Go - язык программирования Go» . golang.org . Проверено 20 апреля 2018 года .
  29. ^ Ульман, Джеффри Д. (1998). Элементы программирования машинного обучения: издание ML97 . Энглвуд Клиффс, Нью-Джерси: Прентис Холл. ISBN 0-13-790387-1.
  30. ^ Айверсон, Кеннет Э. (1962). Язык программирования . Джон Вили и сыновья. ISBN 0-471-43014-5. Архивировано из оригинала на 2009-06-04 . Проверено 9 мая 2010 .
  31. ^ Грэм, Пол (1996). ANSI Common Lisp . Нью-Джерси: Прентис-Холл. ISBN 0-13-370875-6.
  32. ^ Стил, Гай Л. (1990). Common Lisp: язык . Лексингтон: Цифровая пресса. ISBN 1-55558-041-6.
  33. ^ Дибвиг, Р. Кент (1996). Язык программирования схемы: Схема ANSI . Нью-Джерси: Прентис-Холл. ISBN 0-13-454646-6.
  34. ^ Смит, Джерри Д. (1988). Введение в схему . Нью-Джерси: Прентис-Холл. ISBN 0-13-496712-7.
  35. ^ Абельсон, Гарольд; Сассман, Джеральд Джей; Суссман, Джули (1996). Структура и интерпретация компьютерных программ . Нью-Джерси: Макгроу-Хилл. ISBN 0-07-000484-6.