Zadanie (informatyka) - Assignment (computer science)

W programowania , AN przypisaniem zbiorów i / lub ponowne ustawianie wartość zapamiętana w komórce pamięci (S), oznaczone przez zmienną nazwy ; innymi słowy, kopiuje wartość do zmiennej. W większości imperatywnych języków programowania instrukcja przypisania (lub wyrażenie) jest podstawową konstrukcją.

Obecnie najczęściej używanym zapisem dla tej operacji jest (pierwotnie Superplan 1949–51, spopularyzowany przez Fortran 1957 i C ). Drugim najczęściej używanym zapisem jest (pierwotnie ALGOL 1958, spopularyzowany przez Pascala ). W użyciu jest również wiele innych notacji. W niektórych językach używany symbol jest traktowany jako operator (co oznacza, że ​​instrukcja przypisania jako całość zwraca wartość). Inne języki definiują przypisanie jako instrukcję (co oznacza, że ​​nie można jej użyć w wyrażeniu). x = exprx := expr

Przypisania zazwyczaj pozwalają zmiennej na przechowywanie różnych wartości w różnych momentach życia i zasięgu . Jednak niektóre języki (przede wszystkim języki ściśle funkcjonalne ) nie pozwalają na tego rodzaju „destrukcyjne” ponowne przypisanie, ponieważ może to sugerować zmiany nielokalnego stanu. Celem jest wymuszenie przezroczystości referencyjnej , tj. funkcji, które nie zależą od stanu niektórych zmiennych, ale dają takie same wyniki dla danego zestawu parametrów wejściowych w dowolnym momencie. Nowoczesne programy w innych językach również często wykorzystują podobne strategie, chociaż mniej rygorystyczne i tylko w niektórych częściach, w celu zmniejszenia złożoności, zwykle w połączeniu z uzupełniającymi metodologiami, takimi jak strukturyzacja danych , programowanie strukturalne i orientacja obiektowa .

Semantyka

Operacja przypisania jest procesem w programowaniu imperatywnym, w którym różne wartości są kojarzone z określoną nazwą zmiennej w miarę upływu czasu. Program w takim modelu działa zmieniając swój stan za pomocą kolejnych instrukcji przypisania. Prymitywy imperatywnych języków programowania polegają na przypisaniu do wykonania iteracji . Na najniższym poziomie przypisanie jest realizowane za pomocą operacji maszynowych, takich jak MOVElub STORE.

Zmienne to pojemniki na wartości. Istnieje możliwość wprowadzenia wartości do zmiennej, a następnie zastąpienia jej nową. Operacja przypisania modyfikuje bieżący stan wykonywanego programu. W związku z tym przypisanie jest zależne od pojęcia zmiennych . W zadaniu:

  • Oceniany expressionjest w aktualnym stanie programu.
  • variableMa wartość obliczoną, zastępując wcześniejsze wartość tej zmiennej.

Przykład: Zakładając, że ajest to zmienna liczbowa, przypisanie a := 2*aoznacza, że ​​zawartość zmiennej ajest podwojona po wykonaniu instrukcji.

Przykładowy segment kodu C :

int x = 10; 
float y;
x = 23;
y = 32.4f;

W tym przykładzie zmienna xjest najpierw zadeklarowana jako int, a następnie przypisywana jest jej wartość 10. Zauważ, że deklaracja i przypisanie występują w tej samej instrukcji. W drugim wierszu yjest zadeklarowany bez przypisania. W trzecim wierszu xprzypisywana jest wartość 23. Na koniec yprzypisywana jest wartość 32,4.

W przypadku operacji przypisania konieczne jest, aby wartość the expressionbyła dobrze zdefiniowana (jest to poprawna rvalue ) i że variablereprezentuje modyfikowalną jednostkę (jest to poprawna modyfikowalna (nie const ) lvalue ). W niektórych językach, zazwyczaj dynamicznych , nie jest konieczne deklarowanie zmiennej przed przypisaniem jej wartości. W takich językach zmienna jest automatycznie deklarowana przy pierwszym przypisaniu, a zakres, w którym jest deklarowana, zależy od języka.

Pojedyncze zadanie

Każde przypisanie zmieniające istniejącą wartość (np. x := x + 1) jest niedozwolone w językach czysto funkcjonalnych . W programowaniu funkcjonalnym odradza się przypisanie na rzecz przypisania pojedynczego, zwanego także inicjalizacją . Pojedyncze przypisanie jest przykładem powiązania nazwy i różni się od przypisania opisanego w tym artykule tym, że można je wykonać tylko raz, zwykle podczas tworzenia zmiennej; żadna późniejsza zmiana przydziału nie jest dozwolona.

Ocena wyrażenia nie ma efektu ubocznego, jeśli nie zmienia obserwowalnego stanu maszyny i generuje te same wartości dla tych samych danych wejściowych. Przypisanie imperatywne może wprowadzić efekty uboczne podczas niszczenia i czyniąc starą wartość niedostępną podczas zastępowania jej nową i jest z tego powodu określane jako przypisanie destrukcyjne w LISP i programowaniu funkcjonalnym , podobne do niszczącej aktualizacji .

Pojedyncze przypisanie jest jedyną formą przypisania dostępną w czysto funkcjonalnych językach, takich jak Haskell , które nie mają zmiennych w sensie imperatywnych języków programowania, ale raczej nazwane wartościami stałymi, prawdopodobnie o charakterze złożonym, z ich elementami stopniowo definiowanymi na żądanie . Języki czysto funkcjonalne mogą zapewnić możliwość równoległego wykonywania obliczeń , unikając wąskiego gardła von Neumanna związanego z sekwencyjnym wykonywaniem jednego kroku w czasie, ponieważ wartości są od siebie niezależne.

Nieczyste języki funkcyjne zapewniają zarówno pojedyncze przypisanie, jak i prawdziwe przypisanie (chociaż prawdziwe przypisanie jest zwykle używane z mniejszą częstotliwością niż w imperatywnych językach programowania). Na przykład, w programie Scheme, zarówno pojedyncze przypisanie (z let) jak i prawdziwe przypisanie (z set!) mogą być użyte dla wszystkich zmiennych, a wyspecjalizowane prymitywy są dostarczane do destrukcyjnej aktualizacji wewnątrz list, wektorów, łańcuchów itp. W OCaml dozwolone jest tylko pojedyncze przypisanie dla zmiennych za pomocą składni; jednak destrukcyjną aktualizację można stosować na elementach tablic i łańcuchów z oddzielnym operatorem, a także na polach rekordów i obiektów, które zostały wyraźnie zadeklarowane przez programistę jako zmienne (tzn. można je zmienić po ich początkowej deklaracji). let name = value<-

Funkcjonalne języki programowania, które używają pojedynczego przypisania to Clojure (dla struktur danych, nie vars), Erlang (przyjmuje wielokrotne przypisanie, jeśli wartości są równe, w przeciwieństwie do Haskella), F# , Haskell , JavaScript (dla stałych), Lava , OCaml , Oz (dla zmiennych przepływu danych, a nie komórek), Racket (dla niektórych struktur danych, takich jak listy, a nie symbole), SASL , Scala (dla wartości), SISAL , Standard ML . Nie- backtracking Prolog kod można uznać wyraźne pojedynczego przydziału, wyraźny w tym sensie, że jego (nazwane) zmienne mogą być w stan wyraźnie nieprzydzielone lub być ustawiony dokładnie raz. W Haskell natomiast nie może być zmiennych nieprzypisanych, a każda zmienna może być traktowana jako niejawnie ustawiona na jej wartość (lub raczej na obiekt obliczeniowy, który wytworzy swoją wartość na żądanie ) podczas jej tworzenia.

Wartość przydziału

W niektórych językach programowania instrukcja przypisania zwraca wartość, podczas gdy w innych nie.

W większości języków programowania zorientowanych na wyrażenia (na przykład C ) instrukcja przypisania zwraca przypisaną wartość, umożliwiając stosowanie takich idiomów jak x = y = a, w których instrukcja przypisania y = azwraca wartość a, która jest następnie przypisywana do x. W instrukcji, takiej jak , wartość zwracana przez funkcję służy do sterowania pętlą podczas przypisywania tej samej wartości do zmiennej. while ((ch = getchar()) != EOF) {}

W innych językach programowania, na przykład Scheme , wartość zwracana przypisania jest niezdefiniowana i takie idiomy są nieprawidłowe.

W Haskell nie ma przypisania zmiennych; ale operacje podobne do przypisania (takie jak przypisanie do pola tablicy lub pola o zmiennej strukturze danych) zwykle oceniają typ jednostki , który jest reprezentowany jako (). Ten typ ma tylko jedną możliwą wartość, dlatego nie zawiera informacji. Zazwyczaj jest to typ wyrażenia, który jest oceniany wyłącznie pod kątem skutków ubocznych.

Warianty formy przydziału

Niektóre wzorce użycia są bardzo powszechne i dlatego często mają specjalną składnię do ich obsługi. Są to przede wszystkim cukier składniowy, który zmniejsza nadmiarowość kodu źródłowego, ale także pomaga czytelnikom kodu w zrozumieniu intencji programisty i dostarcza kompilatorowi wskazówki do możliwej optymalizacji.

Rozszerzone zadanie

Przypadek, w którym przypisana wartość zależy od poprzedniej, jest tak powszechny, że wiele języków imperatywnych, zwłaszcza C i większość jego potomków, zapewnia specjalne operatory zwane przypisaniem rozszerzonym , takie jak *=, więc a = 2*azamiast tego można je zapisać jako a *= 2. Poza cukrem składniowym wspomaga to zadanie kompilatora, wyjaśniając, że amożliwa jest modyfikacja zmiennej w miejscu .

Powiązane zadanie

Instrukcja jak w = x = y = znazywana jest przypisaniem łańcuchowym, w którym wartość z zjest przypisana do wielu zmiennych w, x,i y. Przypisania łańcuchowe są często używane do inicjalizacji wielu zmiennych, jak w

a = b = c = d = f = 0

Nie wszystkie języki programowania obsługują przypisanie łańcuchowe. Przypisania łańcuchowe są równoważne sekwencji przypisań, ale strategia oceny różni się w zależności od języka. W przypadku prostych przypisań łańcuchowych, takich jak inicjowanie wielu zmiennych, strategia oceny nie ma znaczenia, ale jeśli cele (wartości l) w przypisaniu są w jakiś sposób połączone, strategia oceny wpływa na wynik.

W niektórych językach programowania ( na przykład C ) przypisania łańcuchowe są obsługiwane, ponieważ przypisania są wyrażeniami i mają wartości. W takim przypadku przypisanie łańcucha można zaimplementować przez przypisanie prawego skojarzenia , a przypisania są wykonywane od prawej do lewej. Na przykład i = arr[i] = f()jest równoważne arr[i] = f(); i = arr[i]. W C++ są one również dostępne dla wartości typów klas poprzez zadeklarowanie odpowiedniego typu zwracanego dla operatora przypisania.

W Pythonie instrukcje przypisania nie są wyrażeniami i dlatego nie mają wartości. Zamiast tego przypisania łańcuchowe to seria instrukcji z wieloma celami dla pojedynczego wyrażenia. Przypisania są wykonywane od lewej do prawej, dzięki czemu i = arr[i] = f()oblicza wyrażenie f(), a następnie przypisuje wynik do lewego docelowego elementu docelowego i, a następnie przypisuje ten sam wynik do następnego elementu docelowego arr[i], przy użyciu nowej wartości i. Jest to zasadniczo równoważne temu, tmp = f(); i = tmp; arr[i] = tmpże dla wartości tymczasowej nie jest tworzona żadna rzeczywista zmienna.

Przypisanie równoległe

Niektóre języki programowania, takie jak APL , Common Lisp , Go , JavaScript (od wersji 1.7), PHP , Maple , Lua , occam 2 , Perl , Python , REBOL , Ruby , i PowerShell umożliwiają równoległe przypisanie kilku zmiennych o składni podobnej do :

a, b := 0, 1

który jednocześnie przypisuje 0 do ai 1 do b. Jest to najczęściej nazywane przypisaniem równoległym ; został wprowadzony w CPL w 1963 roku pod nazwą jednoczesne przypisanie i jest czasami nazywany wielokrotnym przypisaniem , chociaż jest to mylące, gdy jest używane z „pojedynczym przypisaniem”, ponieważ nie są to przeciwieństwa. Jeśli prawa strona przypisania jest pojedynczą zmienną (np. tablicą lub strukturą), funkcja nazywa się przypisaniem rozpakowywania lub destrukturyzowania :

var list := {0, 1}
a, b := list

Lista zostanie rozpakowana tak, że 0 jest przypisane do aa 1 do b. Ponadto,

a, b := b, a

zamienia wartości ai b. W językach bez przypisania równoległego należałoby to napisać, aby użyć zmiennej tymczasowej

var t := a
a := b
b := t

ponieważ a := b; b := apozostawia zarówno ai bz pierwotną wartością b.

Niektóre języki, takie jak Go i Python, łączą równoległe przypisywanie, krotki i automatyczne rozpakowywanie krotek, aby umożliwić wiele wartości zwracanych z jednej funkcji, jak w tym przykładzie Pythona:

def f():
    return 1, 2
a, b = f()

podczas gdy inne języki, takie jak C# , pokazane tutaj, wymagają jawnej konstrukcji krotek i dekonstrukcji z nawiasami:

(a, b) = (b, a);
(string, int) f() => ("foo", 1);
var (a, b) = f();

Stanowi to alternatywę dla używania parametrów wyjściowych do zwracania wielu wartości z funkcji. To datuje się na CLU (1974), a CLU pomogło w ogólnym spopularyzowaniu przydziału równoległego.

C# dodatkowo umożliwia uogólnione przypisanie dekonstrukcji z implementacją zdefiniowaną przez wyrażenie po prawej stronie, ponieważ kompilator wyszukuje odpowiednie wystąpienie lub metodę rozszerzenia Deconstruct w wyrażeniu, które musi mieć parametry wyjściowe dla przypisanych zmiennych. Na przykład jedna z takich metod, która dawałaby klasie, że pojawia się w tym samym zachowaniu, co wartość zwracana f()powyżej, byłaby

void Deconstruct(out string a, out int b) { a = "foo"; b = 1; }

W C i C++ operator przecinka jest podobny do przypisania równoległego, ponieważ pozwala na wystąpienie wielu przypisań w ramach jednej instrukcji, pisząc a = 1, b = 2zamiast a, b = 1, 2. Jest to używane głównie w pętlach for i jest zastępowane przez przypisanie równoległe w innych językach, takich jak Go. Jednak powyższy kod C++ nie zapewnia idealnej jednoczesności, ponieważ prawa strona poniższego kodu a = b, b = a+1jest oceniana po lewej stronie. W językach takich jak Python a, b = b, a+1przypisze dwie zmienne jednocześnie, używając początkowej wartości a do obliczenia nowego b.

Przypisanie a równość

Użycie znaku równości =jako operatora przypisania było często krytykowane ze względu na konflikt z równością jako porównaniem dla równości. Skutkuje to zarówno dezorientacją nowicjuszy w pisaniu kodu, jak i dezorientacją nawet doświadczonych programistów w czytaniu kodu. Użycie równości do przypisania datuje się od języka Superplan Heinza Rutishausera , zaprojektowanego w latach 1949-1951 i szczególnie spopularyzowanego przez Fortrana:

Znanym przykładem złego pomysłu był wybór znaku równości do oznaczenia przypisania. Wraca do Fortran w 1957 roku i został ślepo skopiowany przez armie projektantów języków. Dlaczego to zły pomysł? Ponieważ obala stuletnią tradycję, aby „=” oznaczało porównanie do równości, orzeczenie, które jest albo prawdziwe, albo fałszywe. Ale Fortran sprawił, że oznaczało to zadanie, wymuszanie równości. W tym przypadku operandy mają nierówną podstawę: lewy operand (zmienna) ma być równy operandowi prawemu (wyrażeniu). x = y nie oznacza tego samego co y = x.

—  Niklaus Wirth , Dobre pomysły, po drugiej stronie lustra

Początkujący programiści czasami mylą przypisanie z operatorem relacyjnym równości, ponieważ „=” oznacza równość w matematyce i jest używane do przypisania w wielu językach. Ale przypisanie zmienia wartość zmiennej, podczas gdy testowanie równości sprawdza, czy dwa wyrażenia mają tę samą wartość.

W niektórych językach, takich jak BASIC , pojedynczy znak równości ( "=") jest używany zarówno dla operatora przypisania, jak i relacyjnego operatora równości, z określeniem kontekstu, o który chodzi. Inne języki używają różnych symboli dla dwóch operatorów. Na przykład:

  • W ALGOL i Pascal operatorem przypisania jest dwukropek i znak równości ( ":="), podczas gdy operatorem równości jest pojedynczy znak równości ( "=").
  • W C , operator przypisania jest pojedynczym znakiem równości ( "="), podczas gdy operator równości jest parą znaków równości ( "==").
  • W R operatorem przypisania jest zasadniczo <-, jak w x <- value, ale w pewnych kontekstach można użyć pojedynczego znaku równości.

Podobieństwo w tych dwóch symbolach może prowadzić do błędów, jeśli programista zapomni, która forma (" =", " ==", " :=") jest odpowiednia, lub błędnie wpisze " =", gdy " ==" było zamierzone. Jest to powszechny problem programowania w językach takich jak C (w tym jedna słynna próba backdoora jądra Linuksa), gdzie operator przypisania również zwraca przypisaną wartość (w taki sam sposób, jak funkcja zwraca wartość) i może być poprawnie zagnieżdżony wewnątrz wyrażeń. Jeśli na przykład intencją było porównanie dwóch wartości w ifinstrukcji, przypisanie prawdopodobnie zwróci wartość interpretowaną jako Boolean true, w którym to przypadku thenklauzula zostanie wykonana, powodując nieoczekiwane zachowanie programu. Niektóre procesory językowe (takie jak gcc ) potrafią wykryć takie sytuacje i ostrzec programistę o potencjalnym błędzie.

Notacja

Dwie najczęstsze reprezentacje przypisania kopiowania to znak równości ( =) i dwukropek ( :=). Obie formy mogą semantycznie oznaczać instrukcję przypisania lub operator przypisania (który również ma wartość), w zależności od języka i/lub użycia.

variable = expression Fortran , PL/I , C (i potomkowie, tacy jak C++ , Java itp.), powłoka Bourne , Python , Go (przypisanie do wstępnie zadeklarowanych zmiennych), R , PowerShell , Nim itp.
variable := expression ALGOL (i pochodne), Simula , CPL , BCPL , Pascal (i potomkowie tacy jak Modula ), Mary , PL/M , Ada , Smalltalk , Eiffel , Oberon , Dylan , Seed7 , Python (wyrażenie przypisania), Go (skrót od deklarowanie i definiowanie zmiennej), Io , AMPL , ML , AutoHotkey itp.

Inne możliwości to strzałka w lewo lub słowo kluczowe, chociaż istnieją inne, rzadsze warianty:

variable << expression Magik
variable <- expression F# , OCaml , R , S
variable <<- expression r
assign("variable", expression) r
variableexpression APL , Smalltalk , Programowanie PODSTAWOWE
variable =: expression J
LET variable = expression PODSTAWOWY
let variable := expression XZapytanie
set variable to expression AppleScript
set variable = expression Powłoka C
Set-Variable variable (expression) PowerShell
variable : expression Macsyma, Maxima , K
variable: expression Rebola
var variable expression Język skryptowy mIRC
reference-variable :- reference-expression Symulacja

Matematyczne przypisania pseudokodu są zazwyczaj przedstawiane za pomocą strzałki w lewo.

Niektóre platformy umieszczają wyrażenie po lewej stronie, a zmienną po prawej:

MOVE expression TO variable COBOL
expressionvariable TI-BASIC , Casio BASIC
expression -> variable POP-2 , BETA , R
put expression into variable Kod na żywo
PUT expression IN variable ABC

Niektóre języki zorientowane na wyrażenia, takie jak Lisp i Tcl, jednolicie używają składni prefiksowej (lub postfiksowej) dla wszystkich instrukcji, w tym przypisania.

(setf variable expression) Wspólne seplenienie
(set! variable expression) Schemat
set variable expression Tcl
expression variable ! Naprzód

Zobacz też

Uwagi

Bibliografia