stringtranslate.com

Задание (информатика)

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

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

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

Семантика

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

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

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

Пример фрагмента кода на языке C :

int x = 10 ; float y ; x = 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 (для структур данных, а не vars), Erlang (он принимает множественное присваивание, если значения равны, в отличие от Haskell), F# , Haskell , JavaScript (для констант), Lava, OCaml , Oz (для переменных потока данных, а не ячеек), Racket (для некоторых структур данных, таких как списки, а не символы), SASL , Scala (для vals), SISAL , Standard ML . Код Prolog без возврата можно считать явным одиночным присваиванием, явным в том смысле, что его (именованные) переменные могут находиться в явно неназначенном состоянии или быть заданными ровно один раз. В 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}а, б := список

Список будет распакован так, что 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# кортеж return ( string , int ) f () => ( "foo" , 1 ); var ( a , b ) = f ();         
// Возврат кортежа Rust 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.

Присваивание против равенства

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

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

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

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

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

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

Обозначение

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

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

Математические псевдокодовые назначения обычно изображаются с помощью стрелки, направленной влево.

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

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

Смотрите также

Примечания

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

Ссылки

  1. ^ ab "2cs24 Declarative". www.csc.liv.ac.uk . Архивировано из оригинала 24 апреля 2006 г. Получено 20 апреля 2018 г.
  2. ^ abc "Imperative Programming". uah.edu . Архивировано из оригинала 4 марта 2016 . Получено 20 апреля 2018 .
  3. ^ abc Ruediger-Marcus Flaig (2008). Биоинформатическое программирование на Python: практический курс для начинающих. Wiley-VCH. С. 98–99. ISBN 978-3-527-32094-3. Получено 25 декабря 2010 г.
  4. ^ abcde Пересечение границ: изучение функционального программирования с помощью Haskell Архивировано 19 ноября 2010 г. на Wayback Machine , автор Брюс Тейт
  5. ^ Митчелл, Джон К. (2003). Концепции в языках программирования. Cambridge University Press. стр. 23. ISBN 978-0-521-78098-8. Получено 3 января 2011 г.
  6. ^ "Imperative Programming Languages ​​(IPL)" (PDF) . gwu.edu . Архивировано из оригинала (PDF) 2011-07-16 . Получено 20 апреля 2018 .
  7. ^ Джон К. Митчелл (2003). Концепции в языках программирования. Cambridge University Press. С. 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. ^ INMOS Limited, ред. (1988). Справочное руководство Occam 2. Нью-Джерси: Prentice Hall. ISBN 0-13-629312-3.
  13. ^ Уолл, Ларри ; Кристиансен, Том; Шварц, Рэндал К. (1996). Язык программирования Perl (2-е изд.). Кембридж: O´Reilly. ISBN 1-56592-149-6.
  14. ^ Лутц, Марк (2001). Язык программирования Python (2-е изд.). Севастополь: O´Reilly. ISBN 0-596-00085-5.
  15. ^ Томас, Дэвид; Хант, Эндрю (2001). Программирование на Ruby: Руководство прагматичного программиста. Верхняя Сэддл-Ривер: Addison Wesley. ISBN 0-201-71089-7.
  16. ^ DW Barron et al. , «Основные особенности CPL», Computer Journal 6 :2:140 (1963). полный текст (подписка)
  17. ^ "PEP 3132 -- Extended Iterable Unpacking". legacy.python.org . Получено 20 апреля 2018 г. .
  18. ^ "Destructuring assignment". MDN Web Docs . Получено 20 апреля 2018 г.
  19. ^ "Деконструкция кортежей и других типов". Microsoft Docs . Microsoft . Получено 29 августа 2019 .
  20. ^ Эффективный Go: for, «Наконец, в Go нет оператора запятой, а ++ и -- являются операторами, а не выражениями. Таким образом, если вы хотите запустить несколько переменных в for, вам следует использовать параллельное присваивание (хотя это исключает ++ и --)».
  21. ^ Никлаус Вирт. «Хорошие идеи, сквозь зеркало». CiteSeerX 10.1.1.88.8309 . 
  22. ^ "Язык программирования C++. Основы". ntu.edu.sg . 2013-06-01 . Получено 2024-06-21 .
  23. ^ Корбет (6 ноября 2003 г.). "Попытка бэкдора ядра". lwn.net . Получено 21.06.2024 .
  24. ^ "Параметры статического анализатора (использование коллекции компиляторов GNU (GCC))". gcc.gnu.org . Получено 2024-06-21 .
  25. ^ Дейтел, Пол; Дейтел, Харви (2022-10-25). "Управляющие операторы C++, часть 2". Domyassignments . Получено 2024-06-21 .
  26. ^ Мур, Лоури (1980). Основы программирования на языке Паскаль . Нью-Йорк: John Wiley & Sons. ISBN 0-470-26939-1.
  27. ^ Мейер, Бертран (1992). Eiffel the Language . Хемел Хемпстед: Prentice Hall International (Великобритания). ISBN 0-13-247925-7.
  28. ^ Винер, Ричард (1996). Объектно-ориентированное введение в информатику с использованием Eiffel . Upper Saddle River, Нью-Джерси: Prentice Hall. ISBN 0-13-183872-5.
  29. ^ Файнберг, Нил; Кин, Соня Э.; Мэтьюз, Роберт О.; Витингтон, П. Такер (1997). Программирование Дилана . Массачусетс: Addison Wesley. ISBN 0-201-47976-1.
  30. ^ "PEP 572 – Выражения присваивания". python.org . 28 февраля 2018 г. . Получено 4 марта 2020 г. .
  31. ^ "Спецификация языка программирования Go - Язык программирования Go". golang.org . Получено 20 апреля 2018 г. .
  32. ^ Ульман, Джеффри Д. (1998). Элементы программирования ML: издание ML97 . Энглвуд Клиффс, Нью-Джерси: Prentice Hall. ISBN 0-13-790387-1.
  33. ^ Айверсон, Кеннет Э. (1962). Язык программирования. John Wiley and Sons. ISBN 0-471-43014-5. Архивировано из оригинала 2009-06-04 . Получено 2010-05-09 .
  34. ^ Грэм, Пол (1996). ANSI Common Lisp. Нью-Джерси: Prentice Hall. ISBN 0-13-370875-6.
  35. ^ Стил, Гай Л. (1990). Common Lisp: Язык . Лексингтон: Digital Press. ISBN 1-55558-041-6.
  36. ^ Dybvig, R. Kent (1996). Язык программирования Scheme: ANSI Scheme . Нью-Джерси: Prentice Hall. ISBN 0-13-454646-6.
  37. ^ Смит, Джерри Д. (1988). Введение в схему . Нью-Джерси: Prentice Hall. ISBN 0-13-496712-7.
  38. ^ Абельсон, Гарольд; Сассман, Джеральд Джей; Сассман, Джули (1996). Структура и интерпретация компьютерных программ . Нью-Джерси: McGraw-Hill. ISBN 0-07-000484-6.